Merge "[a11y] fix the issue where launcher announces item added to homescreen before it does" into ub-launcher3-master
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 8db875b..555cc73 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -169,7 +169,7 @@
         <activity
             android:name="com.android.launcher3.settings.SettingsActivity"
             android:label="@string/settings_button_text"
-            android:theme="@android:style/Theme.DeviceDefault.Settings"
+            android:theme="@style/HomeSettingsTheme"
             android:autoRemoveFromRecents="true">
             <intent-filter>
                 <action android:name="android.intent.action.APPLICATION_PREFERENCES" />
diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml
index fae1eff..f84a82e 100644
--- a/go/AndroidManifest.xml
+++ b/go/AndroidManifest.xml
@@ -22,7 +22,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="com.android.launcher3" >
 
-    <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="25"/>
+    <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
 
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
diff --git a/go/quickstep/res/values/override.xml b/go/quickstep/res/values/override.xml
index 7636fb3..bb267a3 100644
--- a/go/quickstep/res/values/override.xml
+++ b/go/quickstep/res/values/override.xml
@@ -22,5 +22,7 @@
   <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
 
   <string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
+
+  <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
 </resources>
 
diff --git a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
new file mode 100644
index 0000000..e7099ec
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -0,0 +1,27 @@
+/*
+ * 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.util;
+
+import com.android.launcher3.Launcher;
+
+/** Empty class, only exists so that lowRamWithQuickstepIconRecentsDebug compiles. */
+public class ShelfPeekAnim {
+    public ShelfPeekAnim(Launcher launcher) {
+    }
+
+    public enum ShelfAnimState {
+    }
+}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index 535c5d8..8bd9dba 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -44,6 +44,7 @@
     private final PackageManager mPm;
     private final ColorExtractor mColorExtractor;
     private boolean mDisableColorExtractor;
+    private boolean mBadgeOnLeft = false;
 
     protected final int mFillResIconDpi;
     protected final int mIconBitmapSize;
@@ -77,6 +78,7 @@
     protected void clear() {
         mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
         mDisableColorExtractor = false;
+        mBadgeOnLeft = false;
     }
 
     public ShadowGenerator getShadowGenerator() {
@@ -201,6 +203,13 @@
     }
 
     /**
+     * Switches badging to left/right
+     */
+    public void setBadgeOnLeft(boolean badgeOnLeft) {
+        mBadgeOnLeft = badgeOnLeft;
+    }
+
+    /**
      * Sets the background color used for wrapped adaptive icon
      */
     public void setWrapperBackgroundColor(int color) {
@@ -261,8 +270,12 @@
      */
     public void badgeWithDrawable(Canvas target, Drawable badge) {
         int badgeSize = getBadgeSizeForIconSize(mIconBitmapSize);
-        badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
-                mIconBitmapSize, mIconBitmapSize);
+        if (mBadgeOnLeft) {
+            badge.setBounds(0, mIconBitmapSize - badgeSize, badgeSize, mIconBitmapSize);
+        } else {
+            badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
+                    mIconBitmapSize, mIconBitmapSize);
+        }
         badge.draw(target);
     }
 
diff --git a/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml b/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml
new file mode 100644
index 0000000..cfc6d48
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml
@@ -0,0 +1,8 @@
+<?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/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index 863a8ba..ee672d4 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -28,4 +28,9 @@
     <dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
     <dimen name="swipe_up_y_overshoot">10dp</dimen>
     <dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
+
+    <!-- Predicted icon related -->
+    <dimen name="predicted_icon_background_corner_radius">15dp</dimen>
+    <dimen name="predicted_icon_background_inset">8dp</dimen>
+
 </resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml
index 1ddd3f5..ed3ba92 100644
--- a/quickstep/recents_ui_overrides/res/values/override.xml
+++ b/quickstep/recents_ui_overrides/res/values/override.xml
@@ -24,5 +24,7 @@
   <string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
 
   <string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
+
+  <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension</string>
 </resources>
 
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 32846dc..fe9fd60 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
@@ -23,7 +23,10 @@
 import android.app.prediction.AppPredictionManager;
 import android.app.prediction.AppPredictor;
 import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
 import android.content.ComponentName;
+import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -60,9 +63,12 @@
     private static final String TAG = "PredictiveHotseat";
     private static final boolean DEBUG = false;
 
+    //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543)
+    private static final int APPTARGET_ACTION_UNPIN = 4;
+
     private static final String PREDICTION_CLIENT = "hotseat";
 
-    private boolean mDragStarted = false;
+    private DropTarget.DragObject mDragObject;
     private int mHotSeatItemsCount;
 
     private Launcher mLauncher;
@@ -84,7 +90,6 @@
         mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
         launcher.getDeviceProfile().inv.addOnChangeListener(this);
         mHotseat.addOnAttachStateChangeListener(this);
-        createPredictor();
     }
 
     @Override
@@ -101,7 +106,7 @@
      * Fills gaps in the hotseat with predictions
      */
     public void fillGapsWithPrediction(boolean animate) {
-        if (mDragStarted) {
+        if (mDragObject != null) {
             return;
         }
         List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
@@ -139,6 +144,7 @@
                 PopupContainerWithArrow.showForIcon((BubbleTextView) v);
                 return true;
             });
+            icon.setBackgroundResource(R.drawable.predicted_icon_background);
         }
     }
 
@@ -154,7 +160,10 @@
         }
     }
 
-    private void createPredictor() {
+    /**
+     * Creates App Predictor with all the current apps pinned on the hotseat
+     */
+    public void createPredictor() {
         AppPredictionManager apm = mLauncher.getSystemService(AppPredictionManager.class);
         if (apm == null) {
             return;
@@ -166,12 +175,28 @@
                 new AppPredictionContext.Builder(mLauncher)
                         .setUiSurface(PREDICTION_CLIENT)
                         .setPredictedTargetCount(mHotSeatItemsCount)
+                        .setExtras(getAppPredictionContextExtra())
                         .build());
         mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
                 this::setPredictedApps);
         mAppPredictor.requestPredictionUpdate();
     }
 
+    private Bundle getAppPredictionContextExtra() {
+        Bundle bundle = new Bundle();
+        ViewGroup vg = mHotseat.getShortcutsAndWidgets();
+        ArrayList<AppTarget> pinnedApps = new ArrayList<>();
+        for (int i = 0; i < vg.getChildCount(); i++) {
+            View child = vg.getChildAt(i);
+            if (isPinnedIcon(child)) {
+                WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) child.getTag();
+                pinnedApps.add(getAppTargetFromItemInfo(itemInfo));
+            }
+        }
+        bundle.putParcelableArrayList("pinned_apps", pinnedApps);
+        return bundle;
+    }
+
     private void setPredictedApps(List<AppTarget> appTargets) {
         mComponentKeyMappers.clear();
         for (AppTarget appTarget : appTargets) {
@@ -205,8 +230,11 @@
                 LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
                 workspaceItemInfo.cellX, workspaceItemInfo.cellY);
         ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
+        icon.reset();
         icon.applyFromWorkspaceItem(workspaceItemInfo);
         icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
+        AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo);
+        notifyItemAction(appTarget, AppTargetEvent.ACTION_PIN);
     }
 
     private List<WorkspaceItemInfo> mapToWorkspaceItemInfo(
@@ -268,18 +296,32 @@
         }
     }
 
+    private void notifyItemAction(AppTarget target, int action) {
+        if (mAppPredictor != null) {
+            mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, action).build());
+        }
+    }
+
     @Override
     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
         removePredictedApps(true);
-        mDragStarted = true;
+        mDragObject = dragObject;
     }
 
     @Override
     public void onDragEnd() {
-        if (!mDragStarted) {
+        if (mDragObject == null) {
             return;
         }
-        mDragStarted = false;
+        ItemInfo dragInfo = mDragObject.dragInfo;
+        if (dragInfo instanceof WorkspaceItemInfo && dragInfo.getTargetComponent() != null) {
+            if (isInHotseat(dragInfo) && !isInHotseat(mDragObject.originalDragInfo)) {
+                notifyItemAction(getAppTargetFromItemInfo(dragInfo), AppTargetEvent.ACTION_PIN);
+            } else if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) {
+                notifyItemAction(getAppTargetFromItemInfo(dragInfo), APPTARGET_ACTION_UNPIN);
+            }
+        }
+        mDragObject = null;
         fillGapsWithPrediction(true);
     }
 
@@ -337,4 +379,26 @@
                 && ((WorkspaceItemInfo) view.getTag()).container
                 == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
     }
+
+    private static boolean isPinnedIcon(View view) {
+        if (!(view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo)) {
+            return false;
+        }
+        ItemInfo info = (ItemInfo) view.getTag();
+        return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && (
+                info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+                        || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
+    }
+
+    private static boolean isInHotseat(ItemInfo itemInfo) {
+        return itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+    }
+
+    private static AppTarget getAppTargetFromItemInfo(ItemInfo info) {
+        if (info.getTargetComponent() == null) return null;
+        return new AppTarget.Builder(
+                new AppTargetId("app:" + info.getTargetComponent().getPackageName()),
+                info.getTargetComponent().getPackageName(), info.user).setClassName(
+                info.getTargetComponent().getClassName()).build();
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index d842484..6946508 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -17,7 +17,10 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
@@ -27,12 +30,15 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.SpringAnimationBuilder;
@@ -145,8 +151,37 @@
     @Override
     public Animator createStateElementAnimation(int index, float... values) {
         switch (index) {
-            case INDEX_SHELF_ANIM:
-                return mLauncher.getAllAppsController().createSpringAnimation(values);
+            case INDEX_SHELF_ANIM: {
+                AllAppsTransitionController aatc = mLauncher.getAllAppsController();
+                Animator springAnim = aatc.createSpringAnimation(values);
+
+                if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+                    // Translate hotseat with the shelf until reaching overview.
+                    float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
+                    ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher);
+                    float shiftRange = aatc.getShiftRange();
+                    if (values.length == 1) {
+                        values = new float[] {aatc.getProgress(), values[0]};
+                    }
+                    ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
+                    hotseatAnim.addUpdateListener(anim -> {
+                        float progress = (Float) anim.getAnimatedValue();
+                        if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
+                            float hotseatShift = (progress - overviewProgress) * shiftRange;
+                            mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY);
+                        }
+                    });
+                    hotseatAnim.setInterpolator(LINEAR);
+                    hotseatAnim.setDuration(springAnim.getDuration());
+
+                    AnimatorSet anim = new AnimatorSet();
+                    anim.play(hotseatAnim);
+                    anim.play(springAnim);
+                    return anim;
+                }
+
+                return springAnim;
+            }
             case INDEX_RECENTS_FADE_ANIM:
                 return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
                         RecentsView.CONTENT_ALPHA, values);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index 8338c2e..f9ee701 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -24,11 +24,15 @@
 import android.content.ComponentName;
 import android.content.Context;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.StateListener;
 import com.android.launcher3.Utilities;
@@ -36,12 +40,14 @@
 import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.MainThreadInitializedObject;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.IntStream;
 
 /**
  * Handler responsible to updating the UI due to predicted apps changes. Operations:
@@ -294,6 +300,30 @@
         return mCurrentState;
     }
 
+    /**
+     * Fill in predicted_rank field based on app prediction.
+     * Only applicable when {@link ItemInfo#itemType} is one of the followings:
+     * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
+     * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
+     * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
+     */
+    public static void fillInPredictedRank(
+            @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
+        final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate();
+        if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null
+                || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+                && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
+                && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
+            return;
+        }
+        final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
+        final List<ComponentKeyMapper> predictedApps = manager.getCurrentState().apps;
+        IntStream.range(0, predictedApps.size())
+                .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
+                .findFirst()
+                .ifPresent((rank) -> target.predictedRank = rank);
+    }
+
     public static class PredictionState {
 
         public boolean isEnabled;
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 eefb7dc..9144d5d 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
@@ -195,7 +195,7 @@
     public void finishBindingItems(int pageBoundFirst) {
         super.finishBindingItems(pageBoundFirst);
         if (mHotseatPredictionController != null) {
-            mHotseatPredictionController.fillGapsWithPrediction(false);
+            mHotseatPredictionController.createPredictor();
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index e4e60a0..bb66ae1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -69,7 +69,7 @@
             return super.getOverviewScaleAndTranslation(launcher);
         }
         TaskView dummyTask;
-        if (recentsView.getCurrentPage() >= 0) {
+        if (recentsView.getCurrentPage() >= recentsView.getTaskViewStartIndex()) {
             if (recentsView.getCurrentPage() <= taskCount - 1) {
                 dummyTask = recentsView.getCurrentPageTaskView();
             } else {
@@ -98,7 +98,7 @@
         if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
             // Translate hotseat offscreen if we show it in overview.
             ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher);
-            scaleAndTranslation.translationY = LayoutUtils.getShelfTrackingDistance(launcher,
+            scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher,
                     launcher.getDeviceProfile());
             return scaleAndTranslation;
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 93d4de1..25eaab1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -32,7 +32,6 @@
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
 
-import android.content.Context;
 import android.graphics.Rect;
 import android.view.View;
 
@@ -47,6 +46,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
@@ -91,8 +91,19 @@
     @Override
     public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
         if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
-            // If the hotseat icons are visible in overview, keep them in their normal position.
-            return super.getWorkspaceScaleAndTranslation(launcher);
+            DeviceProfile dp = launcher.getDeviceProfile();
+            if (dp.allAppsIconSizePx >= dp.iconSizePx) {
+                return new ScaleAndTranslation(1, 0, 0);
+            } else {
+                float scale = ((float) dp.allAppsIconSizePx) / dp.iconSizePx;
+                // Distance between the screen center (which is the pivotY for hotseat) and the
+                // bottom of the hotseat (which we want to preserve)
+                float distanceFromBottom = dp.heightPx / 2 - dp.hotseatBarBottomPaddingPx;
+                // On scaling, the bottom edge is moved closer to the pivotY. We move the
+                // hotseat back down so that the bottom edge's position is preserved.
+                float translationY = distanceFromBottom * (1 - scale);
+                return new ScaleAndTranslation(scale, 0, translationY);
+            }
         }
         return getWorkspaceScaleAndTranslation(launcher);
     }
@@ -160,15 +171,7 @@
     }
 
     public static float getDefaultSwipeHeight(Launcher launcher) {
-        return getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
-    }
-
-    public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
-        float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
-        if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) {
-            swipeHeight -= dp.getInsets().bottom;
-        }
-        return swipeHeight;
+        return LayoutUtils.getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 4f50e33..b14da5c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -48,12 +48,12 @@
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
 import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -285,12 +285,18 @@
     public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
         mRecentsAnimationController = null;
         mRecentsAnimationTargets = null;
+        if (mRecentsView != null) {
+            mRecentsView.setRecentsAnimationTargets(null, null);
+        }
     }
 
     @Override
     public void onRecentsAnimationFinished(RecentsAnimationController controller) {
         mRecentsAnimationController = null;
         mRecentsAnimationTargets = null;
+        if (mRecentsView != null) {
+            mRecentsView.setRecentsAnimationTargets(null, null);
+        }
     }
 
     private Rect getStackBounds(DeviceProfile dp) {
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 24f247b..c939de8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -19,10 +19,10 @@
 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
+import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
 import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
-import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 
 import android.animation.Animator;
@@ -33,8 +33,8 @@
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.os.Bundle;
-
 import android.util.ArrayMap;
+
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -428,7 +428,6 @@
     @Override
     public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
         super.onRecentsAnimationCanceled(thumbnailData);
-        mRecentsView.setRecentsAnimationTargets(null, null);
         mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 01cf4bb..844152b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -47,6 +47,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
+import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherInitListener;
@@ -56,12 +57,13 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
@@ -211,7 +213,7 @@
     @Override
     public AnimationFactory prepareRecentsUI(boolean activityVisible,
             boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
-        Launcher launcher = getCreatedActivity();
+        BaseQuickstepLauncher launcher = getCreatedActivity();
         final LauncherState startState = launcher.getStateManager().getState();
 
         LauncherState resetState = startState;
@@ -227,7 +229,7 @@
         launcher.getAppsView().reset(false /* animate */);
 
         return new AnimationFactory() {
-            private ShelfAnimState mShelfState;
+            private final ShelfPeekAnim mShelfAnim = launcher.getShelfPeekAnim();
             private boolean mIsAttachedToWindow;
 
             @Override
@@ -256,30 +258,7 @@
             @Override
             public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
                     long duration) {
-                if (mShelfState == shelfState) {
-                    return;
-                }
-                mShelfState = shelfState;
-                launcher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
-                if (mShelfState == ShelfAnimState.CANCEL) {
-                    return;
-                }
-                float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(launcher);
-                float shelfOverviewProgress = OVERVIEW.getVerticalProgress(launcher);
-                // Peek based on default overview progress so we can see hotseat if we're showing
-                // that instead of predictions in overview.
-                float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(launcher);
-                float shelfPeekingProgress = shelfHiddenProgress
-                        - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f;
-                float toProgress = mShelfState == ShelfAnimState.HIDE
-                        ? shelfHiddenProgress
-                        : mShelfState == ShelfAnimState.PEEK
-                                ? shelfPeekingProgress
-                                : shelfOverviewProgress;
-                Animator shelfAnim = launcher.getStateManager()
-                        .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress);
-                shelfAnim.setInterpolator(interpolator);
-                shelfAnim.setDuration(duration).start();
+                mShelfAnim.setShelfState(shelfState, interpolator, duration);
             }
 
             @Override
@@ -420,8 +399,8 @@
 
     @Nullable
     @Override
-    public Launcher getCreatedActivity() {
-        return Launcher.ACTIVITY_TRACKER.getCreatedActivity();
+    public BaseQuickstepLauncher getCreatedActivity() {
+        return BaseQuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
     }
 
     @Nullable
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 3eb183e..05cec2d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -24,8 +24,6 @@
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
-import static com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState.HIDE;
-import static com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState.PEEK;
 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -33,6 +31,8 @@
 import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 
 import android.animation.Animator;
@@ -69,13 +69,15 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
-import com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState;
 import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.AppWindowAnimationHelper.TargetAlphaProvider;
 import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.SharedApiCompat;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
 import com.android.quickstep.views.LiveTileOverlay;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -151,7 +153,6 @@
             Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
     private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
 
-    private static final long SHELF_ANIM_DURATION = 240;
     public static final long RECENTS_ATTACH_DURATION = 300;
 
     /**
@@ -176,8 +177,6 @@
     private boolean mHasLauncherTransitionControllerStarted;
 
     private AnimationFactory mAnimationFactory = (t) -> { };
-    private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
-    private boolean mLiveTileOverlayAttached = false;
 
     private boolean mWasLauncherAlreadyVisible;
 
@@ -432,7 +431,7 @@
 
     @Override
     public void onMotionPauseChanged(boolean isPaused) {
-        setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION);
+        setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION);
 
         if (mDeviceState.isFullyGesturalNavMode() && isPaused) {
             // In fully gestural nav mode, switch to overview predictions once the user has paused
@@ -547,7 +546,8 @@
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (mRecentsAnimationTargets != null) {
-                mLiveTileOverlay.update(mAppWindowAnimationHelper.getCurrentRectWithInsets(),
+                LiveTileOverlay.getInstance().update(
+                        mAppWindowAnimationHelper.getCurrentRectWithInsets(),
                         mAppWindowAnimationHelper.getCurrentCornerRadius());
             }
         }
@@ -615,7 +615,6 @@
     @Override
     public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
         super.onRecentsAnimationCanceled(thumbnailData);
-        mRecentsView.setRecentsAnimationTargets(null, null);
         mActivityInitListener.unregister();
         mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
         ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
@@ -835,7 +834,7 @@
             setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
             duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
         } else if (endTarget == RECENTS) {
-            mLiveTileOverlay.startIconAnimation();
+            LiveTileOverlay.getInstance().startIconAnimation();
             if (mRecentsView != null) {
                 int nearestPage = mRecentsView.getPageNearestToCenterOfScreen();
                 if (mRecentsView.getNextPage() != nearestPage) {
@@ -1022,6 +1021,7 @@
             // In the off chance that the gesture ends before Launcher is started, we should clear
             // the callback here so that it doesn't update with the wrong state
             mActivity.clearRunOnceOnStartCallback();
+            resetLauncherListenersAndOverlays();
         }
         if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) {
             cancelCurrentAnimation();
@@ -1109,13 +1109,7 @@
         endLauncherTransitionController();
 
         mRecentsView.onGestureAnimationEnd();
-
-        // Reset the callback for deferred activity launches
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mActivityInterface.setOnDeferredActivityLaunchCallback(null);
-        }
-        mActivity.getRootView().setOnApplyWindowInsetsListener(null);
-        removeLiveTileOverlay();
+        resetLauncherListenersAndOverlays();
     }
 
     private void endLauncherTransitionController() {
@@ -1126,6 +1120,15 @@
         }
     }
 
+    private void resetLauncherListenersAndOverlays() {
+        // Reset the callback for deferred activity launches
+        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            mActivityInterface.setOnDeferredActivityLaunchCallback(null);
+        }
+        mActivity.getRootView().setOnApplyWindowInsetsListener(null);
+        removeLiveTileOverlay();
+    }
+
     private void notifyTransitionCancelled() {
         mAnimationFactory.onTransitionCancelled();
     }
@@ -1142,6 +1145,8 @@
         final int runningTaskId = mGestureState.getRunningTaskId();
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (mRecentsAnimationController != null) {
+                SharedApiCompat.setWillFinishToHome(mRecentsAnimationController.getController(),
+                        true /* willFinishToHome */);
                 // Update the screenshot of the task
                 if (mTaskSnapshot == null) {
                     mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
@@ -1230,20 +1235,15 @@
         updateFinalShift();
     }
 
-    private synchronized void addLiveTileOverlay() {
-        if (!mLiveTileOverlayAttached) {
-            mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
-            mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
-            mLiveTileOverlayAttached = true;
+    private void addLiveTileOverlay() {
+        if (LiveTileOverlay.getInstance().attach(mActivity.getRootView().getOverlay())) {
+            mRecentsView.setLiveTileOverlayAttached(true);
         }
     }
 
-    private synchronized void removeLiveTileOverlay() {
-        if (mLiveTileOverlayAttached) {
-            mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
-            mRecentsView.setLiveTileOverlay(null);
-            mLiveTileOverlayAttached = false;
-        }
+    private void removeLiveTileOverlay() {
+        LiveTileOverlay.getInstance().detach(mActivity.getRootView().getOverlay());
+        mRecentsView.setLiveTileOverlayAttached(false);
     }
 
     public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, float expectedAlpha) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 19c289d..92c55da 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -10,7 +10,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
@@ -34,7 +33,7 @@
         switch (method) {
             case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
                 final float swipeHeight =
-                        OverviewState.getDefaultSwipeHeight(mContext, mDeviceProfile);
+                        LayoutUtils.getDefaultSwipeHeight(mContext, mDeviceProfile);
                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
                 return response;
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index 7b8ec4d..e0e20ee 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -89,9 +89,13 @@
             int taskID = intent.getIntExtra(EXTRA_TASK_ID, 0);
             IBinder thumbnail = intent.getExtras().getBinder(EXTRA_THUMBNAIL);
             if (taskID != 0 && thumbnail instanceof ObjectWrapper) {
-                ThumbnailData thumbnailData = ((ObjectWrapper<ThumbnailData>) thumbnail).get();
+                ObjectWrapper<ThumbnailData> obj = (ObjectWrapper<ThumbnailData>) thumbnail;
+                ThumbnailData thumbnailData = obj.get();
                 mFallbackRecentsView.showCurrentTask(taskID);
                 mFallbackRecentsView.updateThumbnail(taskID, thumbnailData);
+                // Clear the ref since any reference to the extras on the system side will still
+                // hold a reference to the wrapper
+                obj.clear();
             }
         }
         intent.removeExtra(EXTRA_TASK_ID);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index 2522c0f..bfe5738 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -172,8 +172,11 @@
                         AppWindowAnimationHelper.TransformParams liveTileParams =
                                 v.getRecentsView().getLiveTileParams(true /* mightNeedToRefill */);
                         if (liveTileParams != null) {
-                            Collections.addAll(surfaceParamsList,
-                                    liveTileAnimationHelper.getSurfaceParams(liveTileParams));
+                            SurfaceParams[] liveTileSurfaceParams =
+                                    liveTileAnimationHelper.getSurfaceParams(liveTileParams);
+                            if (liveTileSurfaceParams != null) {
+                                Collections.addAll(surfaceParamsList, liveTileSurfaceParams);
+                            }
                         }
                     }
                     // Apply surface transform using the surface params list.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
new file mode 100644
index 0000000..b251f9e
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
@@ -0,0 +1,47 @@
+/*
+ * 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.quickstep.logging;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.appprediction.PredictionUiStateManager;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+/**
+ * This class handles AOSP MetricsLogger function calls and logging around
+ * quickstep interactions and app launches.
+ */
+@SuppressWarnings("unused")
+public class UserEventDispatcherAppPredictionExtension extends UserEventDispatcherExtension {
+
+    public static final int ALL_APPS_PREDICTION_TIPS = 2;
+
+    private static final String TAG = "UserEventDispatcher";
+
+    public UserEventDispatcherAppPredictionExtension(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected void onFillInLogContainerData(
+            @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target,
+            @NonNull LauncherLogProto.Target targetParent) {
+        PredictionUiStateManager.fillInPredictedRank(itemInfo, target);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
new file mode 100644
index 0000000..41be683
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -0,0 +1,104 @@
+/*
+ * 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.util;
+
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF_ANIM;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.uioverrides.states.OverviewState;
+
+/**
+ * Animates the shelf between states HIDE, PEEK, and OVERVIEW.
+ */
+public class ShelfPeekAnim {
+
+    public static final Interpolator INTERPOLATOR = OVERSHOOT_1_2;
+    public static final long DURATION = 240;
+
+    private final Launcher mLauncher;
+
+    private ShelfAnimState mShelfState;
+    private boolean mIsPeeking;
+
+    public ShelfPeekAnim(Launcher launcher) {
+        mLauncher = launcher;
+    }
+
+    /**
+     * Animates to the given state, canceling the previous animation if it was still running.
+     */
+    public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
+        if (mShelfState == shelfState) {
+            return;
+        }
+        mLauncher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
+        mShelfState = shelfState;
+        mIsPeeking = mShelfState == ShelfAnimState.PEEK || mShelfState == ShelfAnimState.HIDE;
+        if (mShelfState == ShelfAnimState.CANCEL) {
+            return;
+        }
+        float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(mLauncher);
+        float shelfOverviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
+        // Peek based on default overview progress so we can see hotseat if we're showing
+        // that instead of predictions in overview.
+        float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(mLauncher);
+        float shelfPeekingProgress = shelfHiddenProgress
+                - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f;
+        float toProgress = mShelfState == ShelfAnimState.HIDE
+                ? shelfHiddenProgress
+                : mShelfState == ShelfAnimState.PEEK
+                        ? shelfPeekingProgress
+                        : shelfOverviewProgress;
+        Animator shelfAnim = mLauncher.getStateManager()
+                .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress);
+        shelfAnim.setInterpolator(interpolator);
+        shelfAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mShelfState = ShelfAnimState.CANCEL;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                mIsPeeking = mShelfState == ShelfAnimState.PEEK;
+            }
+        });
+        shelfAnim.setDuration(duration).start();
+    }
+
+    /** @return Whether the shelf is currently peeking or animating to or from peeking. */
+    public boolean isPeeking() {
+        return mIsPeeking;
+    }
+
+    /** The various shelf states we can animate to. */
+    public enum ShelfAnimState {
+        HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false);
+
+        ShelfAnimState(boolean shouldPreformHaptic) {
+            this.shouldPreformHaptic = shouldPreformHaptic;
+        }
+
+        public final boolean shouldPreformHaptic;
+    }
+}
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 ca33605..82fbbc6 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
@@ -326,8 +326,8 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        PluginManagerWrapper.INSTANCE.get(getContext())
-                .addPluginListener(mRecentsExtraCardPluginListener, RecentsExtraCard.class);
+        PluginManagerWrapper.INSTANCE.get(getContext()).addPluginListener(
+                mRecentsExtraCardPluginListener, RecentsExtraCard.class);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
index a838797..18eda60 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
@@ -16,6 +16,7 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.util.FloatProperty;
+import android.view.ViewOverlay;
 
 import com.android.launcher3.anim.Interpolators;
 
@@ -36,6 +37,15 @@
                 }
             };
 
+    private static LiveTileOverlay sInstance;
+
+    public static LiveTileOverlay getInstance() {
+        if (sInstance == null) {
+            sInstance = new LiveTileOverlay();
+        }
+        return sInstance;
+    }
+
     private final Paint mPaint = new Paint();
 
     private Rect mBoundsRect = new Rect();
@@ -46,8 +56,9 @@
 
     private boolean mDrawEnabled = true;
     private float mIconAnimationProgress = 0f;
+    private boolean mIsAttached;
 
-    public LiveTileOverlay() {
+    private LiveTileOverlay() {
         mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
     }
 
@@ -124,6 +135,23 @@
         return PixelFormat.TRANSLUCENT;
     }
 
+    public boolean attach(ViewOverlay overlay) {
+        if (overlay != null && !mIsAttached) {
+            overlay.add(this);
+            mIsAttached = true;
+            return true;
+        }
+
+        return false;
+    }
+
+    public void detach(ViewOverlay overlay) {
+        if (overlay != null) {
+            overlay.remove(this);
+            mIsAttached = false;
+        }
+    }
+
     private void setIconAnimationProgress(float progress) {
         mIconAnimationProgress = progress;
         invalidateSelf();
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 cf616fe..6e7214e 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
@@ -308,7 +308,7 @@
     private final int mEmptyMessagePadding;
     private boolean mShowEmptyMessage;
     private Layout mEmptyTextLayout;
-    private LiveTileOverlay mLiveTileOverlay;
+    private boolean mLiveTileOverlayAttached;
 
     // Keeps track of the index where the first TaskView should be
     private int mTaskViewStartIndex = 0;
@@ -485,6 +485,11 @@
     public void setOverviewStateEnabled(boolean enabled) {
         mOverviewStateEnabled = enabled;
         updateTaskStackListenerState();
+        if (!enabled) {
+            // Reset the running task when leaving overview since it can still have a reference to
+            // its thumbnail
+            mTmpRunningTask = null;
+        }
     }
 
     public void onDigitalWellbeingToastShown() {
@@ -876,8 +881,8 @@
      */
     public void onSwipeUpAnimationSuccess() {
         if (getRunningTaskView() != null) {
-            float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlay != null
-                    ? mLiveTileOverlay.cancelIconAnimation()
+            float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlayAttached
+                    ? LiveTileOverlay.getInstance().cancelIconAnimation()
                     : 0f;
             animateUpRunningTaskIconScale(startProgress);
         }
@@ -1724,13 +1729,13 @@
         mAppWindowAnimationHelper = appWindowAnimationHelper;
     }
 
-    public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) {
-        mLiveTileOverlay = liveTileOverlay;
+    public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) {
+        mLiveTileOverlayAttached = liveTileOverlayAttached;
     }
 
     public void updateLiveTileIcon(Drawable icon) {
-        if (mLiveTileOverlay != null) {
-            mLiveTileOverlay.setIcon(icon);
+        if (mLiveTileOverlayAttached) {
+            LiveTileOverlay.getInstance().setIcon(icon);
         }
     }
 
@@ -1855,8 +1860,8 @@
     private void updateEnabledOverlays() {
         int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
         int taskCount = getTaskViewCount();
-        for (int i = 0; i < taskCount; i++) {
-            getTaskViewAt(i).setOverlayEnabled(i == overlayEnabledPage);
+        for (int i = mTaskViewStartIndex; i < mTaskViewStartIndex + taskCount; i++) {
+            getTaskViewAtByAbsoluteIndex(i).setOverlayEnabled(i == overlayEnabledPage);
         }
     }
 
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 5d9a009..327bb14 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -19,8 +19,6 @@
     <!-- Activity which blocks home gesture -->
     <string name="gesture_blocking_activity" translatable="false"></string>
 
-    <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
-
     <string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
 
     <string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</string>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index fc9cfcd..9ea13c6 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -48,6 +48,7 @@
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
+import com.android.quickstep.util.ShelfPeekAnim;
 
 import java.util.stream.Stream;
 
@@ -64,6 +65,8 @@
             (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha(
                     Float.intBitsToFloat(arg1), arg2 != 0);
 
+    private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this);
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -270,4 +273,8 @@
         return Stream.concat(super.getSupportedShortcuts(),
                 Stream.of(WellbeingModel.SHORTCUT_FACTORY));
     }
+
+    public ShelfPeekAnim getShelfPeekAnim() {
+        return mShelfPeekAnim;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 86ffd22..fd55e07 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -113,16 +114,6 @@
 
     interface AnimationFactory {
 
-        enum ShelfAnimState {
-            HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false);
-
-            ShelfAnimState(boolean shouldPreformHaptic) {
-                this.shouldPreformHaptic = shouldPreformHaptic;
-            }
-
-            public final boolean shouldPreformHaptic;
-        }
-
         default void onRemoteAnimationReceived(RemoteAnimationTargets targets) { }
 
         void createActivityInterface(long transitionLength);
@@ -131,8 +122,8 @@
 
         default void onTransitionCancelled() { }
 
-        default void setShelfState(ShelfAnimState animState, Interpolator interpolator,
-                long duration) { }
+        default void setShelfState(ShelfPeekAnim.ShelfAnimState animState,
+                Interpolator interpolator, long duration) { }
 
         /**
          * @param attached Whether to show RecentsView alongside the app window. If false, recents
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 333e179..46af8bf 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -18,6 +18,7 @@
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
+
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
@@ -185,6 +186,11 @@
         mInputConsumerController.setInputListener(this::onInputConsumerEvent);
     }
 
+    /** @return wrapper controller. */
+    public RecentsAnimationControllerCompat getController() {
+        return mController;
+    }
+
     private void disableInputProxy() {
         if (mInputConsumer != null && mTouchInProgress) {
             long now = SystemClock.uptimeMillis();
diff --git a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
index 4a11601..9ca7f23 100644
--- a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
+++ b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
@@ -20,10 +20,10 @@
 
 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CANCEL_TARGET;
-import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
 import static com.android.systemui.shared.system.LauncherEventUtil.DISMISS;
 import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_QUICK_SCRUB_ONBOARDING_TIP;
 import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_SWIPE_UP_ONBOARDING_TIP;
+import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
 
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 050bdff..2e118b4 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -26,7 +26,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.SysUINavigationMode;
 
 import java.lang.annotation.Retention;
 
@@ -39,12 +39,27 @@
     @IntDef({MULTI_WINDOW_STRATEGY_HALF_SCREEN, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE})
     private @interface MultiWindowStrategy {}
 
+    /**
+     * The height for the swipe up motion
+     */
+    public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
+        float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
+        if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) {
+            swipeHeight -= dp.getInsets().bottom;
+        }
+        return swipeHeight;
+    }
+
     public static void calculateLauncherTaskSize(Context context, DeviceProfile dp, Rect outRect) {
         float extraSpace;
         if (dp.isVerticalBarLayout()) {
             extraSpace = 0;
         } else {
-            extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx;
+            Resources res = context.getResources();
+
+            extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx
+                    + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+                    + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
         }
         calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
     }
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 26e9eaf..0e591ca 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -38,12 +38,12 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.util.LayoutUtils;
 
 /**
  * Scrim used for all-apps and shelf in Overview
@@ -163,7 +163,7 @@
                 int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom
                         + hotseatPadding.bottom + hotseatPadding.top;
                 float dragHandleTop =
-                        Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(context, dp));
+                        Math.min(hotseatSize, LayoutUtils.getDefaultSwipeHeight(context, dp));
                 mDragHandleProgress =  1 - (dragHandleTop / mShiftRange);
             }
             mTopOffset = dp.getInsets().top - mShelfOffset;
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index ca81343..85cffaa 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -31,9 +31,6 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification;
 import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_PRESUBMIT;
-import static com.android.launcher3.util.rule.TestStabilityRule.RUN_FLAFOR;
-import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_PRESUBMIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -63,6 +60,7 @@
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 import com.android.quickstep.views.RecentsView;
 
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
@@ -132,12 +130,8 @@
 
     @NavigationModeSwitch
     @Test
+    @Ignore // b/143488140
     public void goToOverviewFromHome() {
-        // b/142828227
-        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() &&
-                (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) {
-            return;
-        }
         mDevice.pressHome();
         assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
                 mOtherLauncherActivity.packageName)), WAIT_TIME_MS));
@@ -147,12 +141,8 @@
 
     @NavigationModeSwitch
     @Test
+    @Ignore // b/143488140
     public void goToOverviewFromApp() {
-        // b/142828227
-        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() &&
-                (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) {
-            return;
-        }
         startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
 
         mLauncher.getBackground().switchToOverview();
@@ -186,12 +176,8 @@
 
     @NavigationModeSwitch
     @Test
+    @Ignore // b/143488140
     public void testOverview() {
-        // b/142828227
-        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() &&
-                (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) {
-            return;
-        }
         startAppFastAndWaitForRecentTask(getAppPackageName());
         startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
         startTestActivity(2);
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 50cab74..891bf24 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -95,11 +95,6 @@
     @Test
     @NavigationModeSwitch
     public void testStressSwipeToOverview() {
-        // b/142828227
-        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() &&
-                (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) {
-            return;
-        }
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
             closeLauncherActivity();
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 339aef5..80c791c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -130,6 +130,10 @@
         <item name="widgetsTheme">@style/WidgetContainerTheme</item>
     </style>
 
+    <style name="HomeSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings">
+        <item name="android:navigationBarColor">@android:color/transparent</item>
+    </style>
+
     <!--
     Theme overrides to element on homescreen, i.e., which are drawn on top on wallpaper.
     Various foreground colors are overridden to be workspaceTextColor so that they are properly
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 01e9b6e..e6f8a85 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -216,6 +216,7 @@
         cancelDotScaleAnim();
         mDotParams.scale = 0f;
         mForceHideDot = false;
+        setBackground(null);
     }
 
     private void cancelDotScaleAnim() {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index a35f598..bc6fa6e 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -403,14 +403,15 @@
         Point totalWorkspacePadding = getTotalWorkspacePadding();
 
         // Check if the icons fit within the available height.
-        float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize;
-        int maxHeight = availableHeightPx - totalWorkspacePadding.y - folderMargin;
-        float scaleY = maxHeight / usedHeight;
+        float contentUsedHeight = folderCellHeightPx * inv.numFolderRows;
+        int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize
+                - folderMargin;
+        float scaleY = contentMaxHeight / contentUsedHeight;
 
         // Check if the icons fit within the available width.
-        float usedWidth = folderCellWidthPx * inv.numFolderColumns;
-        int maxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
-        float scaleX = maxWidth / usedWidth;
+        float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns;
+        int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
+        float scaleX = contentMaxWidth / contentUsedWidth;
 
         float scale = Math.min(scaleX, scaleY);
         if (scale < 1f) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4811b48..b3b2df6 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -103,6 +103,7 @@
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.FolderNameProvider;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.keyboard.CustomActionsPopup;
@@ -615,6 +616,10 @@
         return mStateManager;
     }
 
+    public FolderNameProvider getFolderNameProvider() {
+        return new FolderNameProvider();
+    }
+
     @Override
     public <T extends View> T findViewById(int id) {
         return mLauncherView.findViewById(id);
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index 8dedc6c..e0c50e2 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -71,8 +71,13 @@
 
         SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
         UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
-        PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context);
+        if (!PackageInstaller.ACTION_SESSION_COMMITTED.equals(intent.getAction())
+                || info == null || user == null) {
+            // Invalid intent.
+            return;
+        }
 
+        PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context);
         if (TextUtils.isEmpty(info.getAppPackageName())
                 || info.getInstallReason() != PackageManager.INSTALL_REASON_USER
                 || packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 431a149..d445bc9 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1278,6 +1278,10 @@
         return !mLauncher.isInState(NORMAL);
     }
 
+    private boolean workspaceInScrollableState() {
+        return mLauncher.isInState(SPRING_LOADED) || !workspaceInModalState();
+    }
+
     /** Returns whether a drag should be allowed to be started from the current workspace state. */
     public boolean workspaceIconsCanBeDragged() {
         return mLauncher.getStateManager().getState().workspaceIconsCanBeDragged;
@@ -2879,7 +2883,7 @@
     @Override
     public boolean scrollLeft() {
         boolean result = false;
-        if (!workspaceInModalState() && !mIsSwitchingState) {
+        if (!mIsSwitchingState && workspaceInScrollableState()) {
             result = super.scrollLeft();
         }
         Folder openFolder = Folder.getOpen(mLauncher);
@@ -2892,7 +2896,7 @@
     @Override
     public boolean scrollRight() {
         boolean result = false;
-        if (!workspaceInModalState() && !mIsSwitchingState) {
+        if (!mIsSwitchingState && workspaceInScrollableState()) {
             result = super.scrollRight();
         }
         Folder openFolder = Folder.getOpen(mLauncher);
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 40c6b5f..7a7e1fe 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
 
 /**
@@ -96,14 +95,13 @@
             propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
 
             if (!hotseat.getRotationMode().isTransposed) {
-                // Set the hotseat's pivot point to match the workspace's, so that it scales together.
-                DragLayer dragLayer = mLauncher.getDragLayer();
-                float[] workspacePivot =
-                        new float[]{ mWorkspace.getPivotX(), mWorkspace.getPivotY() };
-                dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot);
-                dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot);
-                hotseat.setPivotX(workspacePivot[0]);
-                hotseat.setPivotY(workspacePivot[1]);
+                // Set the hotseat's pivot point to match the workspace's, so that it scales
+                // together. Since both hotseat and workspace can move, transform the point
+                // manually instead of using dragLayer.getDescendantCoordRelativeToSelf and
+                // related methods.
+                hotseat.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop() - hotseat.getTop());
+                hotseat.setPivotX(mWorkspace.getPivotX()
+                        + mWorkspace.getLeft() - hotseat.getLeft());
             }
             float hotseatScale = hotseatScaleAndTranslation.scale;
             Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE,
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 3836c9f..08ce9c2 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -2,8 +2,6 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
@@ -134,15 +132,6 @@
         } else {
             mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
         }
-
-        if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
-            // Translate hotseat with the shelf until reaching overview.
-            float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
-            if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
-                float hotseatShift = (progress - overviewProgress) * mShiftRange;
-                mLauncher.getHotseat().setTranslationY(hotseatShift);
-            }
-        }
     }
 
     public float getProgress() {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index d622037..f4b705e 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -110,6 +110,10 @@
             "FAKE_LANDSCAPE_UI", false,
             "Rotate launcher UI instead of using transposed layout");
 
+    public static final TogglableFlag FOLDER_NAME_SUGGEST = new TogglableFlag(
+            "FOLDER_NAME_SUGGEST", true,
+            "Suggests folder names instead of blank text.");
+
     public static final TogglableFlag APP_SEARCH_IMPROVEMENTS = new TogglableFlag(
             "APP_SEARCH_IMPROVEMENTS", false,
             "Adds localized title and keyword search and ranking");
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 65d593c..6b9e205 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -74,7 +74,10 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ClipPathView;
@@ -401,7 +404,6 @@
             mFolderName.setText("");
             mFolderName.setHint(R.string.folder_hint_text);
         }
-
         // In case any children didn't come across during loading, clean up the folder accordingly
         mFolderIcon.post(() -> {
             if (getItemCount() <= 1) {
@@ -410,6 +412,22 @@
         });
     }
 
+
+    /**
+     * Show suggested folder title.
+     */
+    public void showSuggestedTitle(CharSequence suggestName) {
+        if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) {
+            if (!TextUtils.isEmpty(suggestName)) {
+                mFolderName.setHint(suggestName);
+                mFolderName.setText(suggestName);
+                mFolderName.showKeyboard();
+                mInfo.title = suggestName;
+            }
+            animateOpen();
+        }
+    }
+
     /**
      * Creates a new UserFolder, inflated from R.layout.user_folder.
      *
@@ -532,8 +550,6 @@
         // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
         mDeleteFolderOnDropCompleted = false;
 
-        centerAboutIcon();
-
         AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator();
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -546,7 +562,11 @@
                 mState = STATE_OPEN;
                 announceAccessibilityChanges();
 
-                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("folder opened");
+                mLauncher.getUserEventDispatcher().logActionOnItem(
+                        Touch.TAP,
+                        Direction.NONE,
+                        ItemType.FOLDER_ICON, mInfo.cellX, mInfo.cellY);
+
                 mContent.setFocusOnFirstChild();
             }
         });
@@ -592,7 +612,6 @@
         if (mDragController.isDragging()) {
             mDragController.forceTouchMove();
         }
-
         mContent.verifyVisibleHighResIcons(mContent.getNextPage());
     }
 
@@ -877,7 +896,6 @@
         // Reordering may have occured, and we need to save the new item locations. We do this once
         // at the end to prevent unnecessary database operations.
         updateItemLocationsInDatabaseBatch();
-
         // Use the item count to check for multi-page as the folder UI may not have
         // been refreshed yet.
         if (getItemCount() <= mContent.itemsPerPage()) {
@@ -975,8 +993,8 @@
 
     private int getContentAreaHeight() {
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        int maxContentAreaHeight = grid.availableHeightPx
-                - grid.getTotalWorkspacePadding().y - mFooterHeight;
+        int maxContentAreaHeight = grid.availableHeightPx - grid.getTotalWorkspacePadding().y
+                - mFooterHeight;
         int height = Math.min(maxContentAreaHeight,
                 mContent.getDesiredHeight());
         return Math.max(height, MIN_CONTENT_DIMEN);
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 3840639..fd6d1e3 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -58,12 +58,14 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.FolderDotInfo;
 import com.android.launcher3.dragndrop.BaseItemDragListener;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.IconLabelDotView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -368,12 +370,17 @@
 
             if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
             final int finalIndex = index;
-            postDelayed(new Runnable() {
-                public void run() {
-                    mPreviewItemManager.hidePreviewItem(finalIndex, false);
-                    mFolder.showItem(item);
-                    invalidate();
-                }
+
+            String[] suggestedNameOut = new String[1];
+            if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+                Executors.UI_HELPER_EXECUTOR.post(() -> mLauncher.getFolderNameProvider()
+                        .getSuggestedFolderName(getContext(), mInfo.contents, suggestedNameOut));
+            }
+            postDelayed(() -> {
+                mPreviewItemManager.hidePreviewItem(finalIndex, false);
+                mFolder.showItem(item);
+                invalidate();
+                mFolder.showSuggestedTitle(suggestedNameOut[0]);
             }, DROP_IN_ANIMATION_DURATION);
         } else {
             addItem(item);
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
new file mode 100644
index 0000000..0a1221e
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -0,0 +1,61 @@
+/*
+ * 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.launcher3.folder;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.WorkspaceItemInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Locates provider for the folder name.
+ */
+public class FolderNameProvider {
+
+    /**
+     * Returns suggested folder name.
+     */
+    public CharSequence getSuggestedFolderName(Context context,
+            ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] suggestName) {
+        // Currently only run the algorithm on initial folder creation.
+        // For more than 2 items in the folder, the ranking algorithm for finding
+        // candidate folder name should be rewritten.
+        if (workspaceItemInfos.size() == 2) {
+            ComponentName cmp1 = workspaceItemInfos.get(0).getTargetComponent();
+            ComponentName cmp2 = workspaceItemInfos.get(1).getTargetComponent();
+
+            String pkgName0 = cmp1 == null ? "" : cmp1.getPackageName();
+            String pkgName1 = cmp2 == null ? "" : cmp2.getPackageName();
+            // If the two icons are from the same package,
+            // then assign the main icon's name
+            if (pkgName0.equals(pkgName1)) {
+                WorkspaceItemInfo wInfo0 = workspaceItemInfos.get(0);
+                WorkspaceItemInfo wInfo1 = workspaceItemInfos.get(1);
+                if (workspaceItemInfos.get(0).itemType == Favorites.ITEM_TYPE_APPLICATION) {
+                    suggestName[0] = wInfo0.title;
+                } else if (wInfo1.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+                    suggestName[0] = wInfo1.title;
+                }
+                return suggestName[0];
+                // two icons are all shortcuts. Don't assign title
+            }
+        }
+        return suggestName[0];
+    }
+}
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index 9b75b43..598792a 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -44,6 +44,7 @@
 public class LoggerUtils {
     private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>();
     private static final String UNKNOWN = "UNKNOWN";
+    private static final int DEFAULT_PREDICTED_RANK = -100;
 
     public static String getFieldName(int value, Class c) {
         SparseArray<String> cache;
@@ -90,7 +91,7 @@
     }
 
     public static String getTargetStr(Target t) {
-        if (t == null){
+        if (t == null) {
             return "";
         }
         String str = "";
@@ -137,17 +138,16 @@
         if (t.intentHash != 0) {
             typeStr += ", intentHash=" + t.intentHash;
         }
-        if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0) &&
-                t.itemType != ItemType.TASK) {
+        if (t.itemType == ItemType.FOLDER_ICON) {
+            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;
-
+                    + "), span(" + t.spanX + "," + t.spanY + "), pageIdx=" + t.pageIndex;
         }
         if (t.searchQueryLength != 0) {
             typeStr += ", searchQueryLength=" + t.searchQueryLength;
         }
-
         if (t.itemType == ItemType.TASK) {
             typeStr += ", pageIdx=" + t.pageIndex;
         }
@@ -168,17 +168,17 @@
 
     public static Target newItemTarget(ItemInfo info, InstantAppResolver instantAppResolver) {
         Target t = newTarget(Target.Type.ITEM);
-
         switch (info.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                 t.itemType = (instantAppResolver != null && info instanceof AppInfo
-                        && instantAppResolver.isInstantApp(((AppInfo) info)) )
+                        && instantAppResolver.isInstantApp(((AppInfo) info)))
                         ? ItemType.WEB_APP
                         : ItemType.APP_ICON;
-                t.predictedRank = -100; // Never assigned
+                t.predictedRank = DEFAULT_PREDICTED_RANK;
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                 t.itemType = ItemType.SHORTCUT;
+                t.predictedRank = DEFAULT_PREDICTED_RANK;
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                 t.itemType = ItemType.FOLDER_ICON;
@@ -188,6 +188,7 @@
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                 t.itemType = ItemType.DEEPSHORTCUT;
+                t.predictedRank = DEFAULT_PREDICTED_RANK;
                 break;
         }
         return t;
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 21ca74e..99906fe 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -26,6 +26,8 @@
 import static com.android.launcher3.logging.LoggerUtils.newTarget;
 import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
 
+import static java.util.Optional.ofNullable;
+
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -35,6 +37,7 @@
 import android.util.Log;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DropTarget;
@@ -58,7 +61,7 @@
 /**
  * Manages the creation of {@link LauncherEvent}.
  * To debug this class, execute following command before side loading a new apk.
- *
+ * <p>
  * $ adb shell setprop log.tag.UserEvent VERBOSE
  */
 public class UserEventDispatcher implements ResourceBasedOverride {
@@ -94,19 +97,26 @@
 
     /**
      * Fills in the container data on the given event if the given view is not null.
+     *
      * @return whether container data was added.
      */
-    public static boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) {
+    public boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) {
         // Fill in grid(x,y), pageIndex of the child and container type of the parent
         LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v);
         if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
             return false;
         }
-        ItemInfo itemInfo = (ItemInfo) v.getTag();
-        provider.fillInLogContainerData(v, itemInfo, event.srcTarget[0], event.srcTarget[1]);
+        final ItemInfo itemInfo = (ItemInfo) v.getTag();
+        final Target target = event.srcTarget[0];
+        final Target targetParent = event.srcTarget[1];
+        onFillInLogContainerData(itemInfo, target, targetParent);
+        provider.fillInLogContainerData(v, itemInfo, target, targetParent);
         return true;
     }
 
+    protected void onFillInLogContainerData(
+            @NonNull ItemInfo itemInfo, @NonNull Target target, @NonNull Target targetParent) { }
+
     private boolean mSessionStarted;
     private long mElapsedContainerMillis;
     private long mElapsedSessionMillis;
@@ -139,7 +149,11 @@
         mAppOrTaskLaunch = true;
     }
 
-    public void logActionTip(int actionType, int viewType) { }
+    /**
+     * Dummy method.
+     */
+    public void logActionTip(int actionType, int viewType) {
+    }
 
     @Deprecated
     public void logTaskLaunchOrDismiss(int action, int direction, int taskIndex,
@@ -184,15 +198,15 @@
 
     public void logActionCommand(int command, int srcContainerType, int dstContainerType) {
         logActionCommand(command, newContainerTarget(srcContainerType),
-                dstContainerType >=0 ? newContainerTarget(dstContainerType) : null);
+                dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null);
     }
 
     public void logActionCommand(int command, int srcContainerType, int dstContainerType,
-                                 int pageIndex) {
+            int pageIndex) {
         Target srcTarget = newContainerTarget(srcContainerType);
         srcTarget.pageIndex = pageIndex;
         logActionCommand(command, srcTarget,
-                dstContainerType >=0 ? newContainerTarget(dstContainerType) : null);
+                dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null);
     }
 
     public void logActionCommand(int command, Target srcTarget, Target dstTarget) {
@@ -241,7 +255,7 @@
     }
 
     public void logActionOnControl(int action, int controlType, int parentContainer,
-                                   int grandParentContainer){
+            int grandParentContainer) {
         LauncherEvent event = newLauncherEvent(newTouchAction(action),
                 newControlTarget(controlType),
                 newContainerTarget(parentContainer),
@@ -250,11 +264,11 @@
     }
 
     public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer,
-                                   int parentContainerType) {
+            int parentContainerType) {
         final LauncherEvent event = (controlInContainer == null && parentContainerType < 0)
                 ? newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL))
                 : newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL),
-                        newTarget(Target.Type.CONTAINER));
+                newTarget(Target.Type.CONTAINER));
         event.srcTarget[0].controlType = controlType;
         if (controlInContainer != null) {
             fillInLogContainerData(event, controlInContainer);
@@ -301,9 +315,9 @@
      * (1) WORKSPACE: if the launcher is the foreground activity
      * (2) APP: if another app was the foreground activity
      */
-    public void logStateChangeAction(int action, int dir, int downX, int downY, int srcChildTargetType,
-                                     int srcParentContainerType, int dstContainerType,
-                                     int pageIndex) {
+    public void logStateChangeAction(int action, int dir, int downX, int downY,
+            int srcChildTargetType, int srcParentContainerType, int dstContainerType,
+            int pageIndex) {
         LauncherEvent event;
         if (srcChildTargetType == LauncherLogProto.ItemType.TASK) {
             event = newLauncherEvent(newTouchAction(action),
@@ -326,9 +340,25 @@
     }
 
     public void logActionOnItem(int action, int dir, int itemType) {
+        logActionOnItem(action, dir, itemType, null, null);
+    }
+
+    /**
+     * Creates new {@link LauncherEvent} of ITEM target type with input arguments and dispatches it.
+     *
+     * @param touchAction ENUM value of {@link LauncherLogProto.Action.Touch} Action
+     * @param dir         ENUM value of {@link LauncherLogProto.Action.Direction} Action
+     * @param itemType    ENUM value of {@link LauncherLogProto.ItemType}
+     * @param gridX       Nullable X coordinate of item's position on the workspace grid
+     * @param gridY       Nullable Y coordinate of item's position on the workspace grid
+     */
+    public void logActionOnItem(int touchAction, int dir, int itemType,
+            @Nullable Integer gridX, @Nullable Integer gridY) {
         Target itemTarget = newTarget(Target.Type.ITEM);
         itemTarget.itemType = itemType;
-        LauncherEvent event = newLauncherEvent(newTouchAction(action), itemTarget);
+        ofNullable(gridX).ifPresent(value -> itemTarget.gridX = value);
+        ofNullable(gridY).ifPresent(value -> itemTarget.gridY = value);
+        LauncherEvent event = newLauncherEvent(newTouchAction(touchAction), itemTarget);
         event.action.dir = dir;
         dispatchUserEvent(event, null);
     }
@@ -351,7 +381,7 @@
         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
                 newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
                 newTarget(Target.Type.CONTAINER));
-        event.destTarget = new Target[] {
+        event.destTarget = new Target[]{
                 newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
                 newDropTarget(dropTargetAsView)
         };
@@ -373,14 +403,10 @@
         int actionTouch = isButton ? Action.Touch.TAP : Action.Touch.SWIPE;
         Action action = newCommandAction(actionTouch);
         action.command = Action.Command.BACK;
-        action.dir = isButton
-                ? Action.Direction.NONE
-                : gestureSwipeLeft
-                        ? Action.Direction.LEFT
-                        : Action.Direction.RIGHT;
-        Target target = newControlTarget(isButton
-                ? LauncherLogProto.ControlType.BACK_BUTTON
-                : LauncherLogProto.ControlType.BACK_GESTURE);
+        action.dir = isButton ? Action.Direction.NONE :
+                gestureSwipeLeft ? Action.Direction.LEFT : Action.Direction.RIGHT;
+        Target target = newControlTarget(isButton ? LauncherLogProto.ControlType.BACK_BUTTON :
+                LauncherLogProto.ControlType.BACK_GESTURE);
         target.spanX = downX;
         target.spanY = downY;
         target.cardinality = completed ? 1 : 0;
@@ -391,6 +417,7 @@
 
     /**
      * Currently logs following containers: workspace, allapps, widget tray.
+     *
      * @param reason
      */
     public final void resetElapsedContainerMillis(String reason) {
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 0e73829..059ad18 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -115,8 +115,8 @@
         switch (message.what) {
             case MSG_NOTIFICATION_POSTED: {
                 StatusBarNotification sbn = (StatusBarNotification) message.obj;
-                mUiHandler.obtainMessage(shouldBeFilteredOut(sbn)
-                                ? MSG_NOTIFICATION_REMOVED : MSG_NOTIFICATION_POSTED,
+                mUiHandler.obtainMessage(notificationIsValidForUI(sbn)
+                                ? MSG_NOTIFICATION_POSTED : MSG_NOTIFICATION_REMOVED,
                         toKeyPair(sbn)).sendToTarget();
                 return true;
             }
@@ -148,7 +148,7 @@
                 if (sIsConnected) {
                     try {
                         activeNotifications = Arrays.stream(getActiveNotifications())
-                                .filter(this::shouldBeFilteredOut)
+                                .filter(this::notificationIsValidForUI)
                                 .collect(Collectors.toList());
                     } catch (SecurityException ex) {
                         Log.e(TAG, "SecurityException: failed to fetch notifications");
@@ -305,22 +305,22 @@
     }
 
     /**
-     * Returns true for notifications that don't have an intent
-     * or are headers for grouped notifications and should be filtered out.
+     * Returns true for notifications that have an intent and are not headers for grouped
+     * notifications and should be shown in the notification popup.
      */
     @WorkerThread
-    private boolean shouldBeFilteredOut(StatusBarNotification sbn) {
+    private boolean notificationIsValidForUI(StatusBarNotification sbn) {
         Notification notification = sbn.getNotification();
         updateGroupKeyIfNecessary(sbn);
 
         getCurrentRanking().getRanking(sbn.getKey(), mTempRanking);
         if (!mTempRanking.canShowBadge()) {
-            return true;
+            return false;
         }
         if (mTempRanking.getChannel().getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
             // Special filtering for the default, legacy "Miscellaneous" channel.
             if ((notification.flags & Notification.FLAG_ONGOING_EVENT) != 0) {
-                return true;
+                return false;
             }
         }
 
@@ -328,7 +328,7 @@
         CharSequence text = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
         boolean missingTitleAndText = TextUtils.isEmpty(title) && TextUtils.isEmpty(text);
         boolean isGroupHeader = (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
-        return (isGroupHeader || missingTitleAndText);
+        return !isGroupHeader && !missingTitleAndText;
     }
 
     private static Pair<PackageUserKey, NotificationKeyData> toKeyPair(StatusBarNotification sbn) {
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 64df384..40e267b 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -173,6 +173,12 @@
                 mLeaks.add(new View(mContext));
                 break;
             }
+
+            case TestProtocol.REQUEST_ICON_HEIGHT: {
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        mDeviceProfile.allAppsCellHeightPx);
+                break;
+            }
         }
         return response;
     }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 1cfa4af..832f7f0 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -63,6 +63,8 @@
             "all-apps-to-overview-swipe-height";
     public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT =
             "home-to-all-apps-swipe-height";
+    public static final String REQUEST_ICON_HEIGHT =
+            "icon-height";
     public static final String REQUEST_HOTSEAT_TOP = "hotseat-top";
     public static final String REQUEST_IS_LAUNCHER_INITIALIZED = "is-launcher-initialized";
     public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list";
diff --git a/src/com/android/launcher3/util/ObjectWrapper.java b/src/com/android/launcher3/util/ObjectWrapper.java
index b692431..e5b4707 100644
--- a/src/com/android/launcher3/util/ObjectWrapper.java
+++ b/src/com/android/launcher3/util/ObjectWrapper.java
@@ -25,7 +25,7 @@
  */
 public class ObjectWrapper<T> extends Binder {
 
-    private final T mObject;
+    private T mObject;
 
     public ObjectWrapper(T object) {
         mObject = object;
@@ -35,6 +35,10 @@
         return mObject;
     }
 
+    public void clear() {
+        mObject = null;
+    }
+
     public static IBinder wrap(Object obj) {
         return new ObjectWrapper<>(obj);
     }
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
index 8af048d..5b33f18 100644
--- a/src/com/android/launcher3/util/ViewPool.java
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -21,12 +21,12 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import com.android.launcher3.util.ViewPool.Reusable;
-
 import androidx.annotation.AnyThread;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
+import com.android.launcher3.util.ViewPool.Reusable;
+
 /**
  * Utility class to maintain a pool of reusable views.
  * During initialization, views are inflated on the background thread.
@@ -58,14 +58,18 @@
         Preconditions.assertUIThread();
         Handler handler = new Handler();
 
+        // LayoutInflater is not thread save as it maintains a global variable 'mConstructorArgs'.
+        // Create a different copy to use on the background thread.
+        LayoutInflater inflater = mInflater.cloneInContext(mInflater.getContext());
+
         // Inflate views on a non looper thread. This allows us to catch errors like calling
         // "new Handler()" in constructor easily.
         new Thread(() -> {
             for (int i = 0; i < initialSize; i++) {
-                T view = inflateNewView();
+                T view = inflateNewView(inflater);
                 handler.post(() -> addToPool(view));
             }
-        }).start();
+        }, "ViewPool-init").start();
     }
 
     @UiThread
@@ -94,12 +98,12 @@
             mCurrentSize--;
             return (T) mPool[mCurrentSize];
         }
-        return inflateNewView();
+        return inflateNewView(mInflater);
     }
 
     @AnyThread
-    private T inflateNewView() {
-        return (T) mInflater.inflate(mLayoutId, mParent, false);
+    private T inflateNewView(LayoutInflater inflater) {
+        return (T) inflater.inflate(mLayoutId, mParent, false);
     }
 
     /**
diff --git a/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java b/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java
index 0ebea3d..cd9f33d 100644
--- a/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java
+++ b/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java
@@ -34,9 +34,9 @@
     /**
      * Sets up the recents overview extra card and fills in data.
      *
-     * @param context Plugin context
+     * @param context     Plugin context
      * @param frameLayout PlaceholderView
-     * @param activity Recents activity to hold extra view
+     * @param activity    Recents activity to hold extra view
      */
     void setupView(Context context, FrameLayout frameLayout, Activity activity);
 }
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 24b5b02..5cf96c8 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -18,7 +18,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="com.android.launcher3.tests">
 
-    <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="25"
+    <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"
               tools:overrideLibrary="android.support.test.uiautomator.v18"/>
 
     <application android:debuggable="true">
diff --git a/tests/dummy_app/AndroidManifest.xml b/tests/dummy_app/AndroidManifest.xml
index 9d0a74a..f00138c 100644
--- a/tests/dummy_app/AndroidManifest.xml
+++ b/tests/dummy_app/AndroidManifest.xml
@@ -21,7 +21,7 @@
      to come from a domain that you own or have control over. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.example.android.aardwolf">
-    <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="21"/>
+    <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="21"/>
     <application android:label="Aardwolf">
         <activity
             android:name="Activity1"
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 191b405..d7096b0 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -18,10 +18,6 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_PRESUBMIT;
-import static com.android.launcher3.util.rule.TestStabilityRule.RUN_FLAFOR;
-import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_PRESUBMIT;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -38,7 +34,6 @@
 import com.android.launcher3.tapl.AppIcon;
 import com.android.launcher3.tapl.AppIconMenu;
 import com.android.launcher3.tapl.AppIconMenuItem;
-import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.tapl.Widgets;
 import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.views.OptionsPopupView;
@@ -177,11 +172,6 @@
 
     @Test
     public void testWorkspace() throws Exception {
-        // b/142828227
-        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess() &&
-                (RUN_FLAFOR & (PLATFORM_PRESUBMIT | UNBUNDLED_PRESUBMIT)) != 0) {
-            return;
-        }
         final Workspace workspace = mLauncher.getWorkspace();
 
         // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
@@ -349,23 +339,6 @@
         }
     }
 
-    /**
-     * Test dragging a custom shortcut to the workspace and launch it.
-     *
-     * A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it.
-     * Custom shortcuts are replaced by deep shortcuts after api 25.
-     */
-    @Test
-    @PortraitLandscape
-    public void testDragCustomShortcut() {
-        if (!TestHelpers.isInLauncherProcess()) return;     // b/143725213
-        mLauncher.getWorkspace().openAllWidgets()
-                .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
-                .dragToWorkspace();
-        mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut")
-                .launch(getAppPackageName());
-    }
-
     public static String getAppPackageName() {
         return getInstrumentation().getContext().getPackageName();
     }
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 259f9ed..f9d1d93 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.ui.widget;
 
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
-import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -30,7 +29,6 @@
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -71,4 +69,22 @@
         assertNotNull("Widget not found on the workspace", widget);
         widget.launch(getAppPackageName());
     }
+
+    /**
+     * Test dragging a custom shortcut to the workspace and launch it.
+     *
+     * A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it.
+     * Custom shortcuts are replaced by deep shortcuts after api 25.
+     */
+    @Test
+    @PortraitLandscape
+    public void testDragCustomShortcut() throws Throwable {
+        clearHomescreen();
+        mDevice.pressHome();
+        mLauncher.getWorkspace().openAllWidgets()
+                .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
+                .dragToWorkspace();
+        mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut")
+                .launch(getAppPackageName());
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index cc92327..3bdeb14 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -29,6 +29,8 @@
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.testing.TestProtocol;
 
+import java.util.stream.Collectors;
+
 /**
  * Operations on AllApps opened from Home. Also a parent for All Apps opened from Overview.
  */
@@ -36,6 +38,7 @@
     private static final int MAX_SCROLL_ATTEMPTS = 40;
 
     private final int mHeight;
+    private final int mIconHeight;
 
     AllApps(LauncherInstrumentation launcher) {
         super(launcher);
@@ -46,6 +49,9 @@
         // Wait for the recycler to populate.
         mLauncher.waitForObjectInContainer(appListRecycler, By.clazz(TextView.class));
         verifyNotFrozen("All apps freeze flags upon opening all apps");
+        mIconHeight = mLauncher.getTestInfo(
+                TestProtocol.REQUEST_ICON_HEIGHT).
+                getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
     @Override
@@ -62,12 +68,16 @@
         }
         final Rect iconBounds = icon.getVisibleBounds();
         LauncherInstrumentation.log("hasClickableIcon: icon bounds: " + iconBounds);
+        if (iconBounds.height() < mIconHeight / 2) {
+            LauncherInstrumentation.log("hasClickableIcon: icon has insufficient height");
+            return false;
+        }
         if (iconCenterInSearchBox(allAppsContainer, icon)) {
             LauncherInstrumentation.log("hasClickableIcon: icon center is under search box");
             return false;
         }
         if (iconBounds.bottom > displayBottom) {
-            LauncherInstrumentation.log("hasClickableIcon: icon center bellow bottom offset");
+            LauncherInstrumentation.log("hasClickableIcon: icon bottom below bottom offset");
             return false;
         }
         LauncherInstrumentation.log("hasClickableIcon: icon is clickable");
@@ -100,11 +110,6 @@
                     ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
             int deviceHeight = mLauncher.getDevice().getDisplayHeight();
             int displayBottom = deviceHeight - bottomGestureMargin;
-            allAppsContainer.setGestureMargins(
-                    0,
-                    getSearchBox(allAppsContainer).getVisibleBounds().bottom + 1,
-                    0,
-                    bottomGestureMargin);
             final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
             if (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
                     displayBottom)) {
@@ -116,7 +121,12 @@
                             displayBottom)) {
                         mLauncher.scrollToLastVisibleRow(
                                 allAppsContainer,
-                                mLauncher.getObjectsInContainer(allAppsContainer, "icon"),
+                                mLauncher.getObjectsInContainer(allAppsContainer, "icon")
+                                        .stream()
+                                        .filter(icon ->
+                                                icon.getVisibleBounds().bottom
+                                                        <= displayBottom)
+                                        .collect(Collectors.toList()),
                                 searchBox.getVisibleBounds().bottom
                                         - allAppsContainer.getVisibleBounds().top);
                         final int newScroll = getAllAppsScroll();
@@ -163,7 +173,8 @@
                         "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
                         ++attempts <= MAX_SCROLL_ATTEMPTS);
 
-                mLauncher.scroll(allAppsContainer, Direction.UP, margins, 12);
+                mLauncher.scroll(
+                        allAppsContainer, Direction.UP, margins, 12, false);
             }
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled up")) {
@@ -191,7 +202,7 @@
             final UiObject2 allAppsContainer = verifyActiveContainer();
             // Start the gesture in the center to avoid starting at elements near the top.
             mLauncher.scroll(
-                    allAppsContainer, Direction.DOWN, new Rect(0, 0, 0, mHeight / 2), 10);
+                    allAppsContainer, Direction.DOWN, new Rect(0, 0, 0, mHeight / 2), 10, false);
             verifyActiveContainer();
         }
     }
@@ -205,7 +216,7 @@
             final UiObject2 allAppsContainer = verifyActiveContainer();
             // Start the gesture in the center, for symmetry with forward.
             mLauncher.scroll(
-                    allAppsContainer, Direction.UP, new Rect(0, mHeight / 2, 0, 0), 10);
+                    allAppsContainer, Direction.UP, new Rect(0, mHeight / 2, 0, 0), 10, false);
             verifyActiveContainer();
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 0d9038f..6583d32 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -106,7 +106,7 @@
                 }
 
                 if (mLauncher.isFallbackOverview()) {
-                    mLauncher.linearGesture(startX, startY, endX, endY, 10);
+                    mLauncher.linearGesture(startX, startY, endX, endY, 10, false);
                     new BaseOverview(mLauncher);
                 } else {
                     mLauncher.swipeToState(startX, startY, endX, endY, 10, expectedState);
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 8ccfc05..339e14f 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -55,7 +55,8 @@
             final int leftMargin = mLauncher.getTestInfo(
                     TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN).
                     getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
-            mLauncher.scroll(overview, Direction.LEFT, new Rect(leftMargin + 1, 0, 0, 0), 20);
+            mLauncher.scroll(
+                    overview, Direction.LEFT, new Rect(leftMargin + 1, 0, 0, 0), 20, false);
             verifyActiveContainer();
         }
     }
@@ -89,7 +90,8 @@
             final int rightMargin = mLauncher.getTestInfo(
                     TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN).
                     getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
-            mLauncher.scroll(overview, Direction.RIGHT, new Rect(0, 0, rightMargin + 1, 0), 20);
+            mLauncher.scroll(
+                    overview, Direction.RIGHT, new Rect(0, 0, rightMargin + 1, 0), 20, false);
             verifyActiveContainer();
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 1c851f4..4024ff0 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -544,7 +544,8 @@
                 linearGesture(
                         displaySize.x / 2, displaySize.y - 1,
                         displaySize.x / 2, 0,
-                        ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
+                        ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
+                        false);
                 try (LauncherInstrumentation.Closable c = addContextLayer(
                         "Swiped up from context menu to home")) {
                     waitUntilGone("deep_shortcuts_container");
@@ -696,8 +697,8 @@
         final UiObject2 object = container.wait(
                 Until.findObject(getLauncherObjectSelector(resName)),
                 WAIT_TIME_MS);
-        assertNotNull("Can't find a launcher object id: " + resName + " in container: " +
-                container.getResourceName(), object);
+        assertNotNull("Can't find a view in Launcher, id: " + resName + " in container: "
+                + container.getResourceName(), object);
         return object;
     }
 
@@ -706,8 +707,8 @@
         final UiObject2 object = container.wait(
                 Until.findObject(selector),
                 WAIT_TIME_MS);
-        assertNotNull("Can't find a launcher object id: " + selector + " in container: " +
-                container.getResourceName(), object);
+        assertNotNull("Can't find a view in Launcher, id: " + selector + " in container: "
+                + container.getResourceName(), object);
         return object;
     }
 
@@ -738,7 +739,7 @@
 
     private UiObject2 waitForObjectBySelector(BySelector selector) {
         final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
-        assertNotNull("Can't find a launcher object; selector: " + selector, object);
+        assertNotNull("Can't find a view in Launcher, selector: " + selector, object);
         return object;
     }
 
@@ -769,7 +770,7 @@
 
     void swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState) {
         final Bundle parcel = (Bundle) executeAndWaitForEvent(
-                () -> linearGesture(startX, startY, endX, endY, steps),
+                () -> linearGesture(startX, startY, endX, endY, steps, false),
                 event -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName()),
                 "Swipe failed to receive an event for the swipe end");
         assertEquals("Swipe switched launcher to a wrong state;",
@@ -782,33 +783,38 @@
                 ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources()) + 1;
     }
 
-    int getBottomGestureMargin(UiObject2 container) {
-        return container.getVisibleBounds().bottom - getRealDisplaySize().y
-                + getBottomGestureSize();
+    int getBottomGestureMarginInContainer(UiObject2 container) {
+        final int bottomGestureStartOnScreen = getRealDisplaySize().y - getBottomGestureSize();
+        return container.getVisibleBounds().bottom - bottomGestureStartOnScreen;
     }
 
-    void scrollToLastVisibleRow(UiObject2 container, Collection<UiObject2> items, int topPadding) {
+    void scrollToLastVisibleRow(
+            UiObject2 container,
+            Collection<UiObject2> items,
+            int topPaddingInContainer) {
         final UiObject2 lowestItem = Collections.max(items, (i1, i2) ->
                 Integer.compare(i1.getVisibleBounds().top, i2.getVisibleBounds().top));
 
-        final int gestureStart = lowestItem.getVisibleBounds().top + getTouchSlop();
-        final int distance = gestureStart - container.getVisibleBounds().top - topPadding;
-        final int bottomMargin = container.getVisibleBounds().height() - distance;
+        final int itemRowCurrentTopOnScreen = lowestItem.getVisibleBounds().top;
+        final Rect containerRect = container.getVisibleBounds();
+        final int itemRowNewTopOnScreen = containerRect.top + topPaddingInContainer;
+        final int distance = itemRowCurrentTopOnScreen - itemRowNewTopOnScreen + getTouchSlop();
 
-        // TODO: Make the gesture steps dependent on the distance so that it can run for various
-        //       screen sizes
+        final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container);
         scroll(
                 container,
                 Direction.DOWN,
                 new Rect(
                         0,
+                        containerRect.height() - distance - bottomGestureMarginInContainer,
                         0,
-                        0,
-                        Math.max(bottomMargin, getBottomGestureMargin(container))),
-                80);
+                        bottomGestureMarginInContainer),
+                10,
+                true);
     }
 
-    void scroll(UiObject2 container, Direction direction, Rect margins, int steps) {
+    void scroll(
+            UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown) {
         final Rect rect = container.getVisibleBounds();
         if (margins != null) {
             rect.left += margins.left;
@@ -853,7 +859,7 @@
         }
 
         executeAndWaitForEvent(
-                () -> linearGesture(startX, startY, endX, endY, steps),
+                () -> linearGesture(startX, startY, endX, endY, steps, slowDown),
                 event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
                 "Didn't receive a scroll end message: " + startX + ", " + startY
                         + ", " + endX + ", " + endY);
@@ -861,13 +867,17 @@
 
     // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
     // fixed interval each time.
-    void linearGesture(int startX, int startY, int endX, int endY, int steps) {
+    void linearGesture(int startX, int startY, int endX, int endY, int steps, boolean slowDown) {
         log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY);
         final long downTime = SystemClock.uptimeMillis();
         final Point start = new Point(startX, startY);
         final Point end = new Point(endX, endY);
         sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start);
-        final long endTime = movePointer(downTime, downTime, steps * GESTURE_STEP_MS, start, end);
+        long endTime = movePointer(downTime, downTime, steps * GESTURE_STEP_MS, start, end);
+        if (slowDown) {
+            endTime = movePointer(downTime, endTime + GESTURE_STEP_MS, 5 * GESTURE_STEP_MS, end,
+                    end);
+        }
         sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end);
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 91f0fc4..2ee424b 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -52,7 +52,7 @@
             final Rect taskBounds = mTask.getVisibleBounds();
             final int centerX = taskBounds.centerX();
             final int centerY = taskBounds.centerY();
-            mLauncher.linearGesture(centerX, centerY, centerX, 0, 10);
+            mLauncher.linearGesture(centerX, centerY, centerX, 0, 10, false);
             mLauncher.waitForIdle();
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 5fcaa55..d208c66 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -48,8 +48,9 @@
             mLauncher.scroll(
                     widgetsContainer,
                     Direction.DOWN,
-                    new Rect(0, 0, 0, mLauncher.getBottomGestureMargin(widgetsContainer) + 1),
-                    FLING_STEPS);
+                    new Rect(0, 0, 0,
+                            mLauncher.getBottomGestureMarginInContainer(widgetsContainer) + 1),
+                    FLING_STEPS, false);
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) {
                 verifyActiveContainer();
             }
@@ -69,7 +70,7 @@
                     widgetsContainer,
                     Direction.UP,
                     new Rect(0, 0, widgetsContainer.getVisibleBounds().width(), 0),
-                    FLING_STEPS);
+                    FLING_STEPS, false);
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) {
                 verifyActiveContainer();
             }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index db3314e..a0d1a5e 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -182,7 +182,7 @@
         final UiObject2 workspace = verifyActiveContainer();
         mLauncher.scroll(workspace, Direction.RIGHT,
                 new Rect(0, 0, mLauncher.getEdgeSensitivityWidth() + 1, 0),
-                FLING_STEPS);
+                FLING_STEPS, false);
         verifyActiveContainer();
     }
 
@@ -194,7 +194,7 @@
         final UiObject2 workspace = verifyActiveContainer();
         mLauncher.scroll(workspace, Direction.LEFT,
                 new Rect(mLauncher.getEdgeSensitivityWidth() + 1, 0, 0, 0),
-                FLING_STEPS);
+                FLING_STEPS, false);
         verifyActiveContainer();
     }