Merge "Fix bug where dragview gets stuck on screen."
diff --git a/proguard.flags b/proguard.flags
index 37b8093..a450183 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -45,9 +45,10 @@
 
 # BUG(70852369): Surpress additional warnings after changing from Proguard to R8
 -dontwarn android.app.**
--dontwarn android.view.**
--dontwarn android.os.**
 -dontwarn android.graphics.**
+-dontwarn android.os.**
+-dontwarn android.view.**
+-dontwarn android.window.**
 
 # Ignore warnings for hidden utility classes referenced from the shared lib
 -dontwarn com.android.internal.util.**
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 8dab915..d95cc01 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -32,6 +32,7 @@
     <uses-permission android:name="android.permission.VIBRATE"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
     <uses-permission android:name="${packageName}.permission.HOTSEAT_EDU" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
 
     <application android:backupAgent="com.android.launcher3.LauncherBackupAgent"
          android:fullBackupOnly="true"
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
new file mode 100644
index 0000000..5f1046d
--- /dev/null
+++ b/quickstep/res/layout/taskbar.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<com.android.launcher3.taskbar.TaskbarContainerView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/taskbar_container"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
+    <com.android.launcher3.taskbar.TaskbarView
+        android:id="@+id/taskbar_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@color/taskbar_background"/>
+
+</com.android.launcher3.taskbar.TaskbarContainerView>
\ No newline at end of file
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 449fe10..3bc8ddc 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -24,4 +24,7 @@
     <color name="all_apps_label_text_dark">#61FFFFFF</color>
     <color name="all_apps_prediction_row_separator">#3c000000</color>
     <color name="all_apps_prediction_row_separator_dark">#3cffffff</color>
+
+    <!-- Taskbar -->
+    <color name="taskbar_background">#101010</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 551f7b0..4272f50 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -119,4 +119,7 @@
 
     <!-- Minimum distance to swipe to trigger accessibility gesture -->
     <dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
+
+    <!-- Taskbar -->
+    <dimen name="taskbar_size">48dp</dimen>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 3643d6d..2518f42 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
 import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_SIZE;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
 
@@ -29,8 +30,11 @@
 import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.view.LayoutInflater;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.popup.SystemShortcut;
@@ -39,7 +43,10 @@
 import com.android.launcher3.statehandlers.BackButtonAlphaHandler;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.taskbar.TaskbarContainerView;
+import com.android.launcher3.taskbar.TaskbarController;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SysUINavigationMode;
@@ -53,7 +60,6 @@
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
-import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.stream.Stream;
@@ -75,6 +81,8 @@
 
     private OverviewActionsView mActionsView;
 
+    private @Nullable TaskbarController mTaskbarController;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -86,6 +94,11 @@
     @Override
     public void onDestroy() {
         SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
+
+        if (mTaskbarController != null) {
+            mTaskbarController.cleanup();
+        }
+
         super.onDestroy();
     }
 
@@ -190,6 +203,29 @@
         mActionsView = findViewById(R.id.overview_actions_view);
         ((RecentsView) getOverviewPanel()).init(mActionsView);
         mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
+
+        addTaskbarIfNecessary();
+    }
+
+    @Override
+    public void onDisplayInfoChanged(DisplayController.Info info, int flags) {
+        super.onDisplayInfoChanged(info, flags);
+        if ((flags & CHANGE_SIZE) != 0) {
+            addTaskbarIfNecessary();
+        }
+    }
+
+    private void addTaskbarIfNecessary() {
+        if (mTaskbarController != null) {
+            mTaskbarController.cleanup();
+            mTaskbarController = null;
+        }
+        if (FeatureFlags.ENABLE_TASKBAR.get() && mDeviceProfile.isTablet) {
+            TaskbarContainerView taskbarContainer = (TaskbarContainerView) LayoutInflater.from(this)
+                    .inflate(R.layout.taskbar, null, false);
+            mTaskbarController = new TaskbarController(this, taskbarContainer);
+            mTaskbarController.init();
+        }
     }
 
     public <T extends OverviewActionsView> T getActionsView() {
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 99c4dba..470a442 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -75,6 +75,7 @@
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.RemoteAnimationTargets;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
@@ -87,6 +88,7 @@
 import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat;
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.RemoteTransitionCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -163,6 +165,9 @@
     private WrappedAnimationRunnerImpl mAppLaunchRunner;
     private WrappedAnimationRunnerImpl mKeyguardGoingAwayRunner;
 
+    private WrappedAnimationRunnerImpl mWallpaperOpenTransitionRunner;
+    private RemoteTransitionCompat mLauncherOpenTransition;
+
     private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationStart(Animator animation) {
@@ -253,6 +258,18 @@
             @NonNull RemoteAnimationTargetCompat[] appTargets,
             @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing);
 
+    private boolean areAllTargetsTranslucent(@NonNull RemoteAnimationTargetCompat[] targets) {
+        boolean isAllOpeningTargetTrs = true;
+        for (int i = 0; i < targets.length; i++) {
+            RemoteAnimationTargetCompat target = targets[i];
+            if (target.mode == MODE_OPENING) {
+                isAllOpeningTargetTrs &= target.isTranslucent;
+            }
+            if (!isAllOpeningTargetTrs) break;
+        }
+        return isAllOpeningTargetTrs;
+    }
+
     /**
      * Compose the animations for a launch from the app icon.
      *
@@ -270,16 +287,8 @@
         mLauncher.getStateManager().setCurrentAnimation(anim);
 
         Rect windowTargetBounds = getWindowTargetBounds(appTargets);
-        boolean isAllOpeningTargetTrs = true;
-        for (int i = 0; i < appTargets.length; i++) {
-            RemoteAnimationTargetCompat target = appTargets[i];
-            if (target.mode == MODE_OPENING) {
-                isAllOpeningTargetTrs &= target.isTranslucent;
-            }
-            if (!isAllOpeningTargetTrs) break;
-        }
         anim.play(getOpeningWindowAnimators(v, appTargets, wallpaperTargets, windowTargetBounds,
-                !isAllOpeningTargetTrs));
+                areAllTargetsTranslucent(appTargets)));
         if (launcherClosing) {
             Pair<AnimatorSet, Runnable> launcherContentAnimator =
                     getLauncherContentAnimator(true /* isAppOpening */,
@@ -445,10 +454,10 @@
     private Animator getOpeningWindowAnimators(View v,
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets,
-            Rect windowTargetBounds, boolean toggleVisibility) {
+            Rect windowTargetBounds, boolean appTargetsAreTranslucent) {
         RectF launcherIconBounds = new RectF();
         FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v,
-                toggleVisibility, launcherIconBounds, true /* isOpening */);
+                !appTargetsAreTranslucent, launcherIconBounds, true /* isOpening */);
         Rect crop = new Rect();
         Matrix matrix = new Matrix();
 
@@ -495,6 +504,7 @@
                 : 0f;
         final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
                 ? 0 : getWindowCornerRadius(mLauncher.getResources());
+        final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius;
 
         appAnimator.addUpdateListener(new MultiValueUpdateListener() {
             FloatProp mDx = new FloatProp(0, prop.dX, 0, prop.xDuration, AGGRESSIVE_EASE);
@@ -507,7 +517,7 @@
 
             FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 0,
                     RADIUS_DURATION, EXAGGERATED_EASE);
-            FloatProp mShadowRadius = new FloatProp(0, mMaxShadowRadius, 0,
+            FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, 0,
                     APP_LAUNCH_DURATION, EXAGGERATED_EASE);
 
             FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd,
@@ -582,10 +592,11 @@
                                 .withCornerRadius(mWindowRadius.value)
                                 .withShadowRadius(mShadowRadius.value);
                     } else {
-                        tmpPos.set(target.position.x, target.position.y);
                         if (target.localBounds != null) {
                             final Rect localBounds = target.localBounds;
                             tmpPos.set(target.localBounds.left, target.localBounds.top);
+                        } else {
+                            tmpPos.set(target.position.x, target.position.y);
                         }
 
                         matrix.setTranslate(tmpPos.x, tmpPos.y);
@@ -657,6 +668,24 @@
     }
 
     /**
+     * Registers remote animations used when closing apps to home screen.
+     */
+    @Override
+    public void registerRemoteTransitions() {
+        if (SEPARATE_RECENTS_ACTIVITY.get()) {
+            return;
+        }
+        if (hasControlRemoteAppTransitionPermission()) {
+            mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */);
+            mLauncherOpenTransition = RemoteAnimationAdapterCompat.buildRemoteTransition(
+                    new WrappedLauncherAnimationRunner<>(mWallpaperOpenTransitionRunner,
+                            false /* startAtFrontOfQueue */));
+            mLauncherOpenTransition.addHomeOpenCheck();
+            SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition);
+        }
+    }
+
+    /**
      * Unregisters all remote animations.
      */
     @Override
@@ -675,6 +704,20 @@
         }
     }
 
+    @Override
+    public void unregisterRemoteTransitions() {
+        if (SEPARATE_RECENTS_ACTIVITY.get()) {
+            return;
+        }
+        if (hasControlRemoteAppTransitionPermission()) {
+            if (mLauncherOpenTransition == null) return;
+            SystemUiProxy.INSTANCE.getNoCreate().unregisterRemoteTransition(
+                    mLauncherOpenTransition);
+            mLauncherOpenTransition = null;
+            mWallpaperOpenTransitionRunner = null;
+        }
+    }
+
     private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) {
         return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode);
     }
@@ -727,12 +770,13 @@
         int duration = CLOSING_TRANSITION_DURATION_MS;
         float windowCornerRadius = mDeviceProfile.isMultiWindowMode
                 ? 0 : getWindowCornerRadius(mLauncher.getResources());
+        float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius;
         closingAnimator.setDuration(duration);
         closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
             FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
             FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7);
             FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR);
-            FloatProp mShadowRadius = new FloatProp(mMaxShadowRadius, 0, 0, duration,
+            FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, 0, duration,
                     DEACCEL_1_7);
 
             @Override
@@ -742,9 +786,10 @@
                     RemoteAnimationTargetCompat target = appTargets[i];
                     SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
 
-                    tmpPos.set(target.position.x, target.position.y);
                     if (target.localBounds != null) {
                         tmpPos.set(target.localBounds.left, target.localBounds.top);
+                    } else {
+                        tmpPos.set(target.position.x, target.position.y);
                     }
 
                     if (target.mode == MODE_CLOSING) {
diff --git a/quickstep/src/com/android/launcher3/search/DeviceSearchAdapterProvider.java b/quickstep/src/com/android/launcher3/search/DeviceSearchAdapterProvider.java
index 9ce196e..3343cf5 100644
--- a/quickstep/src/com/android/launcher3/search/DeviceSearchAdapterProvider.java
+++ b/quickstep/src/com/android/launcher3/search/DeviceSearchAdapterProvider.java
@@ -23,6 +23,8 @@
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
 
+import com.android.app.search.LayoutType;
+import com.android.app.search.ResultType;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsContainerView;
@@ -78,10 +80,10 @@
         SearchTargetHandler
                 payloadResultView =
                 (SearchTargetHandler) holder.itemView;
-        if (FeatureFlags.SEARCH_TARGET_LEGACY.get()) {
+        if (!FeatureFlags.USE_SEARCH_API.get()) {
             payloadResultView.applySearchTarget(item.getSearchTargetLegacy());
         } else {
-            payloadResultView.applySearchTarget(item.getSearchTarget());
+            payloadResultView.applySearchTarget(item.getSearchTarget(), item.getInlineItems());
         }
     }
 
@@ -123,9 +125,24 @@
      * Returns -1 if viewType is not found
      */
     public int getViewTypeForSearchTarget(SearchTarget t) {
-        //TODO: Replace with values from :SearchUi
-        if (t.getResultType() == 1 && t.getLayoutType().equals("icon")) {
-            return VIEW_TYPE_SEARCH_ICON;
+        if (t.getLayoutType().equals(LayoutType.TEXT_HEADER)) {
+            return VIEW_TYPE_SEARCH_CORPUS_TITLE;
+        }
+        switch (t.getResultType()) {
+            case ResultType.APPLICATION:
+                if (t.getLayoutType().equals(LayoutType.ICON_SINGLE_VERTICAL_TEXT)) {
+                    return VIEW_TYPE_SEARCH_ICON;
+                }
+                break;
+            case ResultType.SETTING:
+                if (t.getLayoutType().equals(LayoutType.ICON_SLICE)) {
+                    return VIEW_TYPE_SEARCH_SLICE;
+                }
+                return VIEW_TYPE_SEARCH_ROW;
+            case ResultType.SHORTCUT:
+                return VIEW_TYPE_SEARCH_ICON_ROW;
+            case ResultType.PLAY:
+                return VIEW_TYPE_SEARCH_ROW_WITH_BUTTON;
         }
         return -1;
     }
diff --git a/quickstep/src/com/android/launcher3/search/SearchAdapterItem.java b/quickstep/src/com/android/launcher3/search/SearchAdapterItem.java
index 258d977..65ac3f9 100644
--- a/quickstep/src/com/android/launcher3/search/SearchAdapterItem.java
+++ b/quickstep/src/com/android/launcher3/search/SearchAdapterItem.java
@@ -32,12 +32,16 @@
 import com.android.launcher3.allapps.AllAppsGridAdapter;
 import com.android.systemui.plugins.shared.SearchTargetLegacy;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Extension of AdapterItem that contains an extra payload specific to item
  */
 public class SearchAdapterItem extends AllAppsGridAdapter.AdapterItem {
     private SearchTargetLegacy mSearchTargetLegacy;
     private SearchTarget mSearchTarget;
+    private List<SearchTarget> mInlineItems = new ArrayList<>();
 
 
     private static final int AVAILABLE_FOR_ACCESSIBILITY = VIEW_TYPE_SEARCH_ROW_WITH_BUTTON
@@ -65,6 +69,9 @@
         return mSearchTarget;
     }
 
+    public List<SearchTarget> getInlineItems() {
+        return mInlineItems;
+    }
     @Override
     protected boolean isCountedForAccessibility() {
         return (AVAILABLE_FOR_ACCESSIBILITY & viewType) == viewType;
diff --git a/quickstep/src/com/android/launcher3/search/SearchResultIcon.java b/quickstep/src/com/android/launcher3/search/SearchResultIcon.java
index e4d737c..d5fe0e8 100644
--- a/quickstep/src/com/android/launcher3/search/SearchResultIcon.java
+++ b/quickstep/src/com/android/launcher3/search/SearchResultIcon.java
@@ -31,10 +31,12 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.app.search.ResultType;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.allapps.AllAppsStore;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.AppInfo;
@@ -46,6 +48,7 @@
 import com.android.systemui.plugins.shared.SearchTargetEventLegacy;
 import com.android.systemui.plugins.shared.SearchTargetLegacy;
 
+import java.util.List;
 import java.util.function.Consumer;
 
 /**
@@ -128,10 +131,27 @@
         }
     }
 
+    /**
+     * Applies {@link SearchTarget} to view. registers a consumer after a corresponding
+     * {@link ItemInfoWithIcon} is created
+     */
+    public void applySearchTarget(SearchTarget searchTarget, List<SearchTarget> inlineItems,
+            Consumer<ItemInfoWithIcon> cb) {
+        mOnItemInfoChanged = cb;
+        applySearchTarget(searchTarget, inlineItems);
+    }
+
     @Override
-    public void applySearchTarget(SearchTarget searchTarget) {
-        prepareUsingApp(new ComponentName(searchTarget.getPackageName(),
-                searchTarget.getExtras().getString("class")), searchTarget.getUserHandle());
+    public void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
+        switch (parentTarget.getResultType()) {
+            case ResultType.APPLICATION:
+                prepareUsingApp(new ComponentName(parentTarget.getPackageName(),
+                        parentTarget.getExtras().getString("class")), parentTarget.getUserHandle());
+                break;
+            case ResultType.SHORTCUT:
+                prepareUsingShortcutInfo(parentTarget.getShortcutInfo());
+                break;
+        }
     }
 
     private void prepareUsingApp(ComponentName componentName, UserHandle userHandle) {
@@ -185,7 +205,9 @@
     @Override
     public void handleSelection(int eventType) {
         mLauncher.getItemOnClickListener().onClick(this);
-        reportEvent(eventType);
+        if (!FeatureFlags.USE_SEARCH_API.get()) {
+            reportEvent(eventType);
+        }
     }
 
     private void reportEvent(int eventType) {
diff --git a/quickstep/src/com/android/launcher3/search/SearchResultIconRow.java b/quickstep/src/com/android/launcher3/search/SearchResultIconRow.java
index 8c491d2..80d543a 100644
--- a/quickstep/src/com/android/launcher3/search/SearchResultIconRow.java
+++ b/quickstep/src/com/android/launcher3/search/SearchResultIconRow.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
+import android.app.search.SearchTarget;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ShortcutInfo;
@@ -32,6 +33,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.app.search.ResultType;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
@@ -112,6 +114,16 @@
     }
 
     @Override
+    public void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
+        mResultIcon.applySearchTarget(parentTarget, children, this);
+        if (parentTarget.getResultType() == ResultType.SHORTCUT) {
+            ShortcutInfo shortcutInfo = parentTarget.getShortcutInfo();
+            setProviderDetails(new ComponentName(shortcutInfo.getPackage(), ""),
+                    shortcutInfo.getUserHandle());
+        }
+    }
+
+    @Override
     public void applySearchTarget(SearchTargetLegacy searchTarget) {
         mSearchTarget = searchTarget;
         mResultIcon.applySearchTarget(searchTarget, this);
diff --git a/quickstep/src/com/android/launcher3/search/SearchResultPlayItem.java b/quickstep/src/com/android/launcher3/search/SearchResultPlayItem.java
index 3bb821f..840bde9 100644
--- a/quickstep/src/com/android/launcher3/search/SearchResultPlayItem.java
+++ b/quickstep/src/com/android/launcher3/search/SearchResultPlayItem.java
@@ -17,6 +17,8 @@
 
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
+import android.app.search.SearchAction;
+import android.app.search.SearchTarget;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -27,8 +29,6 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.BitmapDrawable;
-import android.net.Uri;
-import android.os.Bundle;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
@@ -43,12 +43,11 @@
 import com.android.launcher3.R;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.util.Themes;
-import com.android.systemui.plugins.shared.SearchTargetEventLegacy;
-import com.android.systemui.plugins.shared.SearchTargetLegacy;
 
 import java.io.IOException;
 import java.net.URL;
 import java.net.URLConnection;
+import java.util.List;
 
 /**
  * A View representing a PlayStore item.
@@ -67,9 +66,8 @@
     private TextView[] mDetailViews = new TextView[3];
     private Button mPreviewButton;
     private String mPackageName;
-    private boolean mIsInstantGame;
-
-    private SearchTargetLegacy mSearchTarget;
+    private Intent mIntent;
+    private Intent mSecondaryIntent;
 
 
     public SearchResultPlayItem(Context context) {
@@ -93,7 +91,7 @@
         mIconView = findViewById(R.id.icon);
         mTitleView = findViewById(R.id.title_view);
         mPreviewButton = findViewById(R.id.try_button);
-        mPreviewButton.setOnClickListener(view -> launchInstantGame());
+        mPreviewButton.setOnClickListener(view -> launchIntent(mSecondaryIntent));
         mDetailViews[0] = findViewById(R.id.detail_0);
         mDetailViews[1] = findViewById(R.id.detail_1);
         mDetailViews[2] = findViewById(R.id.detail_2);
@@ -101,9 +99,59 @@
         ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams();
         iconParams.height = mDeviceProfile.allAppsIconSizePx;
         iconParams.width = mDeviceProfile.allAppsIconSizePx;
-        setOnClickListener(view -> handleSelection(SearchTargetEventLegacy.SELECT));
+        setOnClickListener(view -> launchIntent(mIntent));
     }
 
+    private void showIfNecessary(TextView textView, @Nullable String string) {
+        if (string == null || string.isEmpty()) {
+            textView.setVisibility(GONE);
+        } else {
+            textView.setText(string);
+            textView.setVisibility(VISIBLE);
+        }
+    }
+
+    private void launchIntent(Intent intent) {
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().startActivity(intent);
+    }
+
+    @Override
+    public void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
+        if (parentTarget.getPackageName().equals(mPackageName)) {
+            return;
+        }
+        mPackageName = parentTarget.getPackageName();
+        SearchAction action = parentTarget.getSearchAction();
+        mTitleView.setText(action.getTitle());
+        showIfNecessary(mDetailViews[0], action.getSubtitle().toString());
+        mIntent = action.getIntent();
+
+        mIconView.setBackgroundResource(R.drawable.ic_deepshortcut_placeholder);
+        loadIcon(action.getIcon().getUri().toString());
+
+        mSecondaryIntent = children.size() == 1 ? children.get(0).getSearchAction().getIntent()
+                : null;
+        mPreviewButton.setVisibility(mSecondaryIntent == null ? GONE : VISIBLE);
+    }
+
+    private void loadIcon(String iconUrl) {
+        UI_HELPER_EXECUTOR.execute(() -> {
+            try {
+                URL url = new URL(iconUrl);
+                URLConnection con = url.openConnection();
+                con.addRequestProperty("Cache-Control", "max-age: 0");
+                con.setUseCaches(true);
+                Bitmap bitmap = BitmapFactory.decodeStream(con.getInputStream());
+                BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), getRoundedBitmap(
+                        Bitmap.createScaledBitmap(bitmap, mDeviceProfile.allAppsIconSizePx,
+                                mDeviceProfile.allAppsIconSizePx, false)));
+                mIconView.post(() -> mIconView.setBackground(bitmapDrawable));
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        });
+    }
 
     private Bitmap getRoundedBitmap(Bitmap bitmap) {
         final int iconSize = bitmap.getWidth();
@@ -124,80 +172,4 @@
         });
         return output;
     }
-
-
-    @Override
-    public void applySearchTarget(SearchTargetLegacy searchTarget) {
-        mSearchTarget = searchTarget;
-        Bundle bundle = searchTarget.getExtras();
-        SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
-        if (bundle.getString("package", "").equals(mPackageName)) {
-            return;
-        }
-        mIsInstantGame = bundle.getBoolean("instant_game", false);
-        mPackageName = bundle.getString("package");
-        mPreviewButton.setVisibility(mIsInstantGame ? VISIBLE : GONE);
-        mTitleView.setText(bundle.getString("title"));
-//        TODO: Should use a generic type to get values b/165320033
-        showIfNecessary(mDetailViews[0], bundle.getString("price"));
-        showIfNecessary(mDetailViews[1], bundle.getString("rating"));
-
-        mIconView.setBackgroundResource(R.drawable.ic_deepshortcut_placeholder);
-        UI_HELPER_EXECUTOR.execute(() -> {
-            try {
-                URL url = new URL(bundle.getString("icon_url"));
-                URLConnection con = url.openConnection();
-//                TODO: monitor memory and investigate if it's better to use glide
-                con.addRequestProperty("Cache-Control", "max-age: 0");
-                con.setUseCaches(true);
-                Bitmap bitmap = BitmapFactory.decodeStream(con.getInputStream());
-                BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), getRoundedBitmap(
-                        Bitmap.createScaledBitmap(bitmap, mDeviceProfile.allAppsIconSizePx,
-                                mDeviceProfile.allAppsIconSizePx, false)));
-                mIconView.post(() -> mIconView.setBackground(bitmapDrawable));
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        });
-    }
-
-    private void showIfNecessary(TextView textView, @Nullable String string) {
-        if (string == null || string.isEmpty()) {
-            textView.setVisibility(GONE);
-        } else {
-            textView.setText(string);
-            textView.setVisibility(VISIBLE);
-        }
-    }
-
-    @Override
-    public void handleSelection(int eventType) {
-        if (mPackageName == null) return;
-        Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(
-                "https://play.google.com/store/apps/details?id="
-                        + mPackageName));
-        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        getContext().startActivity(i);
-        logSearchEvent(eventType);
-    }
-
-    private void launchInstantGame() {
-        if (!mIsInstantGame) return;
-        Intent intent = new Intent(Intent.ACTION_VIEW);
-        String referrer = "Pixel_Launcher";
-        String id = mPackageName;
-        String deepLinkUrl = "market://details?id=" + id + "&launch=true&referrer=" + referrer;
-        intent.setPackage("com.android.vending");
-        intent.setData(Uri.parse(deepLinkUrl));
-        intent.putExtra("overlay", true);
-        intent.putExtra("callerId", getContext().getPackageName());
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        getContext().startActivity(intent);
-        logSearchEvent(SearchTargetEventLegacy.CHILD_SELECT);
-    }
-
-    private void logSearchEvent(int eventType) {
-        SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
-                new SearchTargetEventLegacy.Builder(mSearchTarget, eventType).build());
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/search/SearchResultSettingsSlice.java b/quickstep/src/com/android/launcher3/search/SearchResultSettingsSlice.java
index 80ad305..bf50b67 100644
--- a/quickstep/src/com/android/launcher3/search/SearchResultSettingsSlice.java
+++ b/quickstep/src/com/android/launcher3/search/SearchResultSettingsSlice.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.search;
 
+import android.app.search.SearchTarget;
 import android.content.Context;
 import android.net.Uri;
 import android.util.AttributeSet;
@@ -35,6 +36,8 @@
 import com.android.systemui.plugins.shared.SearchTargetEventLegacy;
 import com.android.systemui.plugins.shared.SearchTargetLegacy;
 
+import java.util.List;
+
 /**
  * A slice view wrapper with settings app icon at start
  */
@@ -89,6 +92,18 @@
     }
 
     @Override
+    public void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
+        reset();
+        try {
+            mSliceLiveData = mLauncher.getLiveSearchManager().getSliceForUri(
+                    parentTarget.getSliceUri());
+            mSliceLiveData.observe(mLauncher, mSliceView);
+        } catch (Exception ex) {
+            Log.e(TAG, "unable to bind slice", ex);
+        }
+    }
+
+    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mSliceView.setOnSliceActionListener(this);
diff --git a/quickstep/src/com/android/launcher3/search/SearchSectionHeaderView.java b/quickstep/src/com/android/launcher3/search/SearchSectionHeaderView.java
index eb40938..ccc38db 100644
--- a/quickstep/src/com/android/launcher3/search/SearchSectionHeaderView.java
+++ b/quickstep/src/com/android/launcher3/search/SearchSectionHeaderView.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.search;
 
+import android.app.search.SearchTarget;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.widget.TextView;
@@ -23,6 +24,8 @@
 
 import com.android.systemui.plugins.shared.SearchTargetLegacy;
 
+import java.util.List;
+
 /**
  * Header text view that shows a title for a given section in All apps search
  */
@@ -53,4 +56,10 @@
             setVisibility(INVISIBLE);
         }
     }
+
+    @Override
+    public void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
+        setText(parentTarget.getSearchAction().getTitle());
+        setVisibility(VISIBLE);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/search/SearchServicePipeline.java b/quickstep/src/com/android/launcher3/search/SearchServicePipeline.java
new file mode 100644
index 0000000..6585213
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/search/SearchServicePipeline.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.search;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.app.search.Query;
+import android.app.search.SearchContext;
+import android.app.search.SearchSession;
+import android.app.search.SearchTarget;
+import android.app.search.SearchUiManager;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.app.search.ResultType;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.AllAppsSectionDecorator;
+import com.android.launcher3.allapps.search.SearchPipeline;
+import com.android.launcher3.allapps.search.SearchSectionInfo;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Search pipeline utilizing {@link android.app.search.SearchUiManager}
+ */
+public class SearchServicePipeline implements SearchPipeline {
+    private static final int SUPPORTED_RESULT_TYPES =
+            ResultType.APPLICATION | ResultType.SHORTCUT | ResultType.PLAY | ResultType.PEOPLE
+                    | ResultType.SETTING;
+    private static final int REQUEST_TIMEOUT = 200;
+    private static final String TAG = "SearchServicePipeline";
+
+
+    private final Context mContext;
+    private final SearchSession mSession;
+    private final DeviceSearchAdapterProvider mAdapterProvider;
+
+    private boolean mCanceled = false;
+
+
+    public SearchServicePipeline(Context context, DeviceSearchAdapterProvider adapterProvider) {
+        mContext = context;
+        mAdapterProvider = adapterProvider;
+        SearchUiManager manager = context.getSystemService(SearchUiManager.class);
+        mSession = manager.createSearchSession(
+                new SearchContext(SUPPORTED_RESULT_TYPES, REQUEST_TIMEOUT, null));
+    }
+
+    @Override
+    public void query(String input, Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> callback,
+            CancellationSignal cancellationSignal) {
+        mCanceled = false;
+        Query query = new Query(input, System.currentTimeMillis(), null);
+        mSession.query(query, UI_HELPER_EXECUTOR, items -> {
+            if (!mCanceled) {
+                callback.accept(this.onResult(items));
+            }
+            Log.w(TAG, "Ignoring results due to cancel signal");
+        });
+    }
+
+    /**
+     * Given A list of search Targets, pairs a group of search targets to a AdapterItem that can
+     * be inflated in AllAppsRecyclerView
+     */
+    private ArrayList<AllAppsGridAdapter.AdapterItem> onResult(List<SearchTarget> searchTargets) {
+        HashMap<String, SearchAdapterItem> adapterMap = new LinkedHashMap<>();
+        List<SearchTarget> unmappedChildren = new ArrayList<>();
+        SearchSectionInfo section = new SearchSectionInfo();
+        section.setDecorationHandler(
+                new AllAppsSectionDecorator.SectionDecorationHandler(mContext, true));
+        for (SearchTarget target : searchTargets) {
+            if (!TextUtils.isEmpty(target.getParentId())) {
+                if (!addChildToParent(target, adapterMap)) {
+                    unmappedChildren.add(target);
+                }
+                continue;
+            }
+            int viewType = mAdapterProvider.getViewTypeForSearchTarget(target);
+            if (viewType != -1) {
+                SearchAdapterItem adapterItem = new SearchAdapterItem(target, viewType);
+                adapterItem.searchSectionInfo = section;
+                adapterMap.put(target.getId(), adapterItem);
+            }
+        }
+        for (SearchTarget s : unmappedChildren) {
+            if (!addChildToParent(s, adapterMap)) {
+                Log.w(TAG,
+                        "Unable to pair child " + s.getId() + " to parent " + s.getParentId());
+            }
+        }
+        return new ArrayList<>(adapterMap.values());
+    }
+
+    /**
+     * Adds a child SearchTarget to a collection of searchTarget children with a shared parentId.
+     * Returns false if no parent searchTarget with id=$parentId does not exists.
+     */
+    private boolean addChildToParent(SearchTarget target, HashMap<String, SearchAdapterItem> map) {
+        if (!map.containsKey(target.getParentId())) return false;
+        map.get(target.getParentId()).getInlineItems().add(target);
+        return true;
+    }
+
+    /**
+     * Unregister callbacks and destroy search session
+     */
+    public void destroy() {
+        mSession.destroy();
+    }
+
+    /**
+     * Cancels current ongoing search request.
+     */
+    public void cancel() {
+        mCanceled = true;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/search/SearchSettingsRowView.java b/quickstep/src/com/android/launcher3/search/SearchSettingsRowView.java
index 8306e3b..6fc0046 100644
--- a/quickstep/src/com/android/launcher3/search/SearchSettingsRowView.java
+++ b/quickstep/src/com/android/launcher3/search/SearchSettingsRowView.java
@@ -19,6 +19,8 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
+import android.app.search.SearchAction;
+import android.app.search.SearchTarget;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -97,6 +99,14 @@
         SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
     }
 
+    @Override
+    public void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
+        SearchAction action = parentTarget.getSearchAction();
+        mIconView.setContentDescription(action.getTitle());
+        showIfAvailable(mTitleView, action.getTitle().toString());
+        showIfAvailable(mBreadcrumbsView, action.getSubtitle().toString());
+    }
+
     private void showIfAvailable(TextView view, @Nullable String string) {
         if (TextUtils.isEmpty(string)) {
             view.setVisibility(GONE);
diff --git a/quickstep/src/com/android/launcher3/search/SearchTargetHandler.java b/quickstep/src/com/android/launcher3/search/SearchTargetHandler.java
index 9ff057f..e72578d 100644
--- a/quickstep/src/com/android/launcher3/search/SearchTargetHandler.java
+++ b/quickstep/src/com/android/launcher3/search/SearchTargetHandler.java
@@ -20,6 +20,8 @@
 
 import com.android.systemui.plugins.shared.SearchTargetLegacy;
 
+import java.util.List;
+
 /**
  * An interface for supporting dynamic search results
  */
@@ -28,13 +30,14 @@
     /**
      * Update view using values from {@link SearchTargetLegacy}
      */
-    void applySearchTarget(SearchTargetLegacy searchTarget);
+    default void applySearchTarget(SearchTargetLegacy searchTarget) {
+    }
+
 
     /**
      * Update view using values from {@link SearchTargetLegacy}
      */
-    default void applySearchTarget(SearchTarget searchTarget){
-
+    default void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
new file mode 100644
index 0000000..0093e66
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 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.taskbar;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder.
+ */
+public class TaskbarContainerView extends FrameLayout {
+    public TaskbarContainerView(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public TaskbarContainerView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TaskbarContainerView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public TaskbarContainerView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
new file mode 100644
index 0000000..7be1b92
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 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.taskbar;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
+import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
+
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.view.Gravity;
+import android.view.WindowManager;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.R;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+/**
+ * Interfaces with Launcher/WindowManager/SystemUI to determine what to show in TaskbarView.
+ */
+public class TaskbarController {
+
+    private static final String WINDOW_TITLE = "Taskbar";
+
+    private final TaskbarContainerView mTaskbarContainerView;
+    private final TaskbarView mTaskbarView;
+    private final BaseQuickstepLauncher mLauncher;
+    private final WindowManager mWindowManager;
+    // Layout width and height of the Taskbar in the default state.
+    private final Point mTaskbarSize;
+
+    private WindowManager.LayoutParams mWindowLayoutParams;
+
+    public TaskbarController(BaseQuickstepLauncher launcher,
+            TaskbarContainerView taskbarContainerView) {
+        mLauncher = launcher;
+        mTaskbarContainerView = taskbarContainerView;
+        mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view);
+        mWindowManager = mLauncher.getWindowManager();
+        mTaskbarSize = new Point(MATCH_PARENT,
+                mLauncher.getResources().getDimensionPixelSize(R.dimen.taskbar_size));
+    }
+
+    /**
+     * Initializes the Taskbar, including adding it to the screen.
+     */
+    public void init() {
+        addToWindowManager();
+    }
+
+    /**
+     * Removes the Taskbar from the screen, and removes any obsolete listeners etc.
+     */
+    public void cleanup() {
+        removeFromWindowManager();
+    }
+
+    private void removeFromWindowManager() {
+        if (mTaskbarContainerView.isAttachedToWindow()) {
+            mWindowManager.removeViewImmediate(mTaskbarContainerView);
+        }
+    }
+
+    private void addToWindowManager() {
+        removeFromWindowManager();
+
+        final int gravity = Gravity.BOTTOM;
+
+        mWindowLayoutParams = new WindowManager.LayoutParams(
+                mTaskbarSize.x,
+                mTaskbarSize.y,
+                TYPE_APPLICATION_OVERLAY,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                PixelFormat.TRANSLUCENT);
+        mWindowLayoutParams.setTitle(WINDOW_TITLE);
+        mWindowLayoutParams.packageName = mLauncher.getPackageName();
+        mWindowLayoutParams.gravity = gravity;
+        mWindowLayoutParams.setFitInsetsTypes(0);
+        mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+        mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+        WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance();
+        wmWrapper.setProvidesInsetsTypes(
+                mWindowLayoutParams,
+                new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT }
+        );
+
+        TaskbarContainerView.LayoutParams taskbarLayoutParams =
+                new TaskbarContainerView.LayoutParams(mTaskbarSize.x, mTaskbarSize.y);
+        taskbarLayoutParams.gravity = gravity;
+        mTaskbarView.setLayoutParams(taskbarLayoutParams);
+
+        mWindowManager.addView(mTaskbarContainerView, mWindowLayoutParams);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
new file mode 100644
index 0000000..5df8d5f
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 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.taskbar;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
+ */
+public class TaskbarView extends LinearLayout {
+    public TaskbarView(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 3be1ced..a00ce56 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -64,7 +64,7 @@
 import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarToOverviewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.UiThreadHelper;
@@ -220,6 +220,7 @@
     @Override
     public void onDestroy() {
         super.onDestroy();
+        getAppsView().getSearchUiManager().destroy();
         if (mHotseatPredictionController != null) {
             mHotseatPredictionController.destroy();
             mHotseatPredictionController = null;
@@ -284,7 +285,7 @@
                 list.add(new NoButtonNavbarToOverviewTouchController(this));
                 break;
             case TWO_BUTTONS:
-                list.add(new TwoButtonNavbarToOverviewTouchController(this));
+                list.add(new TwoButtonNavbarTouchController(this));
                 list.add(getDeviceProfile().isVerticalBarLayout()
                         ? new TransposedQuickSwitchTouchController(this)
                         : new QuickSwitchTouchController(this));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarToOverviewTouchController.java
deleted file mode 100644
index ff4bfe6..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarToOverviewTouchController.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides.touchcontrollers;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
-
-import android.view.MotionEvent;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
-import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
-import com.android.quickstep.SystemUiProxy;
-
-/**
- * Touch controller for handling edge swipes in 2-button mode
- */
-public class TwoButtonNavbarToOverviewTouchController extends AbstractStateChangeTouchController {
-
-    private static final String TAG = "2BtnNavbarTouchCtrl";
-
-    public TwoButtonNavbarToOverviewTouchController(Launcher l) {
-        super(l, l.getDeviceProfile().isVerticalBarLayout()
-                ? SingleAxisSwipeDetector.HORIZONTAL : SingleAxisSwipeDetector.VERTICAL);
-    }
-
-    @Override
-    protected boolean canInterceptTouch(MotionEvent ev) {
-        if (mCurrentAnimation != null) {
-            // If we are already animating from a previous state, we can intercept.
-            return true;
-        }
-        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
-            return false;
-        }
-        return mLauncher.isInState(NORMAL) && (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
-    }
-
-    @Override
-    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-            boolean draggingFromNav =
-                    mLauncher.getDeviceProfile().isSeascape() == isDragTowardPositive;
-            return draggingFromNav ? OVERVIEW : NORMAL;
-        } else {
-            return isDragTowardPositive ? OVERVIEW : NORMAL;
-        }
-    }
-
-    @Override
-    protected float getShiftRange() {
-        return mLauncher.getDeviceProfile().isVerticalBarLayout()
-                ? mLauncher.getDragLayer().getWidth() : super.getShiftRange();
-    }
-
-    @Override
-    protected float initCurrentAnimation(@AnimationFlags int animComponent) {
-        float range = getShiftRange();
-        long maxAccuracy = (long) (2 * range);
-        mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
-                maxAccuracy, animComponent);
-        return (mLauncher.getDeviceProfile().isSeascape() ? 2 : -2) / range;
-    }
-
-    @Override
-    protected void onSwipeInteractionCompleted(LauncherState targetState) {
-        super.onSwipeInteractionCompleted(targetState);
-        if (mStartState == NORMAL && targetState == OVERVIEW) {
-            SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
-        }
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
new file mode 100644
index 0000000..6271a44
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TwoButtonNavbarTouchController.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides.touchcontrollers;
+
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
+import static com.android.launcher3.AbstractFloatingView.getOpenView;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+
+import android.animation.ValueAnimator;
+import android.view.MotionEvent;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
+import com.android.launcher3.touch.AbstractStateChangeTouchController;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.views.AllAppsEduView;
+
+/**
+ * Touch controller for handling edge swipes in 2-button mode
+ */
+public class TwoButtonNavbarTouchController extends AbstractStateChangeTouchController {
+
+    private static final int MAX_NUM_SWIPES_TO_TRIGGER_EDU = 3;
+
+    private static final String TAG = "2BtnNavbarTouchCtrl";
+
+    private final boolean mIsTransposed;
+
+    // If true, we will finish the current animation instantly on second touch.
+    private boolean mFinishFastOnSecondTouch;
+
+    private int mContinuousTouchCount = 0;
+
+    public TwoButtonNavbarTouchController(Launcher l) {
+        super(l, l.getDeviceProfile().isVerticalBarLayout()
+                ? SingleAxisSwipeDetector.HORIZONTAL : SingleAxisSwipeDetector.VERTICAL);
+        mIsTransposed = l.getDeviceProfile().isVerticalBarLayout();
+    }
+
+    @Override
+    protected boolean canInterceptTouch(MotionEvent ev) {
+        boolean canIntercept = canInterceptTouchInternal(ev);
+        if (!canIntercept) {
+            mContinuousTouchCount = 0;
+        }
+        return canIntercept;
+    }
+
+    private boolean canInterceptTouchInternal(MotionEvent ev) {
+        if (mCurrentAnimation != null) {
+            if (mFinishFastOnSecondTouch) {
+                mCurrentAnimation.getAnimationPlayer().end();
+            }
+
+            // If we are already animating from a previous state, we can intercept.
+            return true;
+        }
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+            return false;
+        }
+        if ((ev.getEdgeFlags() & EDGE_NAV_BAR) == 0) {
+            return false;
+        }
+        if (!mIsTransposed && mLauncher.isInState(OVERVIEW)) {
+            return true;
+        }
+        return mLauncher.isInState(NORMAL);
+    }
+
+    @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        if (mIsTransposed) {
+            boolean draggingFromNav =
+                    mLauncher.getDeviceProfile().isSeascape() == isDragTowardPositive;
+            return draggingFromNav ? OVERVIEW : NORMAL;
+        } else {
+            return isDragTowardPositive ^ (fromState == OVERVIEW) ? OVERVIEW : NORMAL;
+        }
+    }
+
+    @Override
+    protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
+            LauncherState targetState, float velocity, boolean isFling) {
+        super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
+                velocity, isFling);
+        mFinishFastOnSecondTouch = !mIsTransposed && mFromState == NORMAL;
+    }
+
+    @Override
+    protected float getShiftRange() {
+        return mLauncher.getDeviceProfile().isVerticalBarLayout()
+                ? mLauncher.getDragLayer().getWidth() : super.getShiftRange();
+    }
+
+    @Override
+    protected float initCurrentAnimation(@AnimationFlags int animComponent) {
+        float range = getShiftRange();
+        long maxAccuracy = (long) (2 * range);
+        mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
+                maxAccuracy, animComponent);
+        return (mLauncher.getDeviceProfile().isSeascape() ? 2 : -2) / range;
+    }
+
+    @Override
+    protected void onSwipeInteractionCompleted(LauncherState targetState) {
+        super.onSwipeInteractionCompleted(targetState);
+        if (!mIsTransposed) {
+            mContinuousTouchCount++;
+        }
+        if (mStartState == NORMAL && targetState == OVERVIEW) {
+            SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
+        } else if (targetState == NORMAL
+                && mContinuousTouchCount >= MAX_NUM_SWIPES_TO_TRIGGER_EDU) {
+            mContinuousTouchCount = 0;
+            if (getOpenView(mLauncher, TYPE_ALL_APPS_EDU) == null) {
+                AllAppsEduView.show(mLauncher);
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 3d5b1c6..d648dd6 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1395,6 +1395,7 @@
                 // Update the screenshot of the task
                 if (mTaskSnapshot == null) {
                     UI_HELPER_EXECUTOR.execute(() -> {
+                        if (mRecentsAnimationController == null) return;
                         final ThumbnailData taskSnapshot =
                                 mRecentsAnimationController.screenshotTask(runningTaskId);
                         MAIN_EXECUTOR.execute(() -> {
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index b4f20d1..5bed929 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -396,4 +396,9 @@
             pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
         }
     }
+
+    /** Called when OverviewService is bound to this process */
+    void onOverviewServiceBound() {
+        // Do nothing
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 901040d..a80c111 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -22,12 +22,14 @@
 import static com.android.launcher3.GestureNavContract.EXTRA_ICON_POSITION;
 import static com.android.launcher3.GestureNavContract.EXTRA_ICON_SURFACE;
 import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK;
+import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
 
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
+import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Matrix;
@@ -126,7 +128,11 @@
         ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
         Intent intent = new Intent(mGestureState.getHomeIntent());
         mActiveAnimationFactory.addGestureContract(intent);
-        mContext.startActivity(intent, options.toBundle());
+        try {
+            mContext.startActivity(intent, options.toBundle());
+        } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
+            mContext.startActivity(createHomeIntent());
+        }
         return mActiveAnimationFactory;
     }
 
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 9f435f5..7630bc4 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -270,4 +270,11 @@
                 + res.getDimensionPixelSize(R.dimen.overview_actions_height);
         return actionsHeight;
     }
-}
\ No newline at end of file
+
+    @Override
+    void onOverviewServiceBound() {
+        final BaseQuickstepLauncher activity = getCreatedActivity();
+        if (activity == null) return;
+        activity.getAppTransitionManager().registerRemoteTransitions();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 7bf7712..fb8f9fe 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -20,6 +20,7 @@
 import static android.content.Intent.ACTION_PACKAGE_CHANGED;
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 
+import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
@@ -74,9 +75,7 @@
     public OverviewComponentObserver(Context context, RecentsAnimationDeviceState deviceState) {
         mContext = context;
         mDeviceState = deviceState;
-        mCurrentHomeIntent = new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_HOME)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mCurrentHomeIntent = createHomeIntent();
         mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(mContext.getPackageName());
         ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
         ComponentName myHomeComponent =
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index c37fd84..7beeae2 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
+import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
@@ -309,9 +310,7 @@
     }
 
     public void startHome() {
-        startActivity(new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_HOME)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        startActivity(createHomeIntent());
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 74b56e9..4301377 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -24,6 +24,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
@@ -383,6 +384,7 @@
      */
     public boolean canStartSystemGesture() {
         boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
+                || (mSystemUiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0
                 || mRotationTouchHelper.isTaskListFrozen();
         return canStartWithNavHidden
                 && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index c0087b0..ca73041 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -293,7 +293,7 @@
             builder.withMatrix(mMatrix)
                     .withWindowCrop(mCropRect)
                     .withCornerRadius(params.getCornerRadius())
-                    .withShadowRadius(params.getShadowRadius());
+                    .withShadowRadius(app.isTranslucent ? 0 : params.getShadowRadius());
         }
 
         @Override
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index a214d81..ca55468 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -35,6 +35,7 @@
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.RemoteTransitionCompat;
 
 /**
  * Holds the reference to SystemUI.
@@ -408,4 +409,26 @@
             }
         }
     }
+
+    @Override
+    public void registerRemoteTransition(RemoteTransitionCompat remoteTransition) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.registerRemoteTransition(remoteTransition);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call registerRemoteTransition");
+            }
+        }
+    }
+
+    @Override
+    public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.unregisterRemoteTransition(remoteTransition);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call registerRemoteTransition");
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index e59035c..196cae7 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -136,6 +136,12 @@
                 SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy);
                 TouchInteractionService.this.initInputMonitor();
                 preloadOverview(true /* fromInit */);
+                mDeviceState.runOnUserUnlocked(() -> {
+                    final BaseActivityInterface ai =
+                            mOverviewComponentObserver.getActivityInterface();
+                    if (ai == null) return;
+                    ai.onOverviewServiceBound();
+                });
             });
             sIsInitialized = true;
         }
@@ -354,7 +360,7 @@
                     getString(R.string.all_apps_label),
                     getString(R.string.all_apps_label),
                     PendingIntent.getActivity(this, SYSTEM_ACTION_ID_ALL_APPS, intent,
-                            PendingIntent.FLAG_UPDATE_CURRENT));
+                            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
             am.registerSystemAction(allAppsAction, SYSTEM_ACTION_ID_ALL_APPS);
         } else {
             am.unregisterSystemAction(SYSTEM_ACTION_ID_ALL_APPS);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 5aaea00..85ecab1 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -19,6 +19,7 @@
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
@@ -203,9 +204,7 @@
                 public void onAnimationEnd(Animator animation) {
                     if (dismissTask) {
                         // For now, just start the home intent so user is prompted to unlock the device.
-                        mContext.startActivity(new Intent(Intent.ACTION_MAIN)
-                                .addCategory(Intent.CATEGORY_HOME)
-                                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+                        mContext.startActivity(createHomeIntent());
                         mHomeLaunched = true;
                     }
                     mStateCallback.setState(STATE_HANDLER_INVALIDATED);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 924b32c..864e08d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -15,10 +15,12 @@
  */
 package com.android.quickstep.inputconsumers;
 
+import static com.android.launcher3.Utilities.createHomeIntent;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
 
+import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.graphics.PointF;
 import android.view.MotionEvent;
@@ -77,7 +79,11 @@
 
     @Override
     public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
-        mContext.startActivity(mGestureState.getHomeIntent());
+        try {
+            mContext.startActivity(mGestureState.getHomeIntent());
+        } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
+            mContext.startActivity(createHomeIntent());
+        }
         ActiveGestureLog.INSTANCE.addLog("startQuickstep");
         BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
         int state = (mGestureState != null && mGestureState.getEndTarget() != null)
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index a89aaf4..a3ee912 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -198,7 +198,6 @@
      */
     public boolean update(
             @SurfaceRotation int touchRotation, @SurfaceRotation int displayRotation) {
-        mRecentsActivityRotation = inferRecentsActivityRotation(displayRotation);
         mDisplayRotation = displayRotation;
         mTouchRotation = touchRotation;
         mPreviousRotation = touchRotation;
@@ -206,6 +205,7 @@
     }
 
     private boolean updateHandler() {
+        mRecentsActivityRotation = inferRecentsActivityRotation(mDisplayRotation);
         if (mRecentsActivityRotation == mTouchRotation
                 || (canRecentsActivityRotate() && (mFlags & FLAG_SWIPE_UP_NOT_RUNNING) != 0)) {
             mOrientationHandler = PagedOrientationHandler.PORTRAIT;
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 5a7f541..65bcf26 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -325,7 +325,7 @@
         builder.withMatrix(mMatrix)
                 .withWindowCrop(mTmpCropRect)
                 .withCornerRadius(getCurrentCornerRadius())
-                .withShadowRadius(params.getShadowRadius());
+                .withShadowRadius(app.isTranslucent ? 0 : params.getShadowRadius());
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
             // When relativeLayer = 0, it reverts the surfaces back to the original order.
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index f281296..2f2b566 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -72,7 +72,6 @@
 import android.text.TextPaint;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
-import android.util.Property;
 import android.util.SparseBooleanArray;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -1515,7 +1514,9 @@
                 }
                 int scrollDiff = newScroll[i] - oldScroll[i] + offset;
                 if (scrollDiff != 0) {
-                    Property translationProperty = mOrientationHandler.getPrimaryViewTranslate();
+                    FloatProperty translationProperty = child instanceof TaskView
+                            ? ((TaskView) child).getPrimaryFillDismissGapTranslationProperty()
+                            : mOrientationHandler.getPrimaryViewTranslate();
 
                     ResourceProvider rp = DynamicResource.provider(mActivity);
                     SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_END)
@@ -1927,7 +1928,11 @@
                             ? modalLeftOffsetSize
                             : modalRightOffsetSize;
             float totalTranslation = translation + modalTranslation;
-            mOrientationHandler.getPrimaryViewTranslate().set(getChildAt(i),
+            View child = getChildAt(i);
+            FloatProperty translationProperty = child instanceof TaskView
+                    ? ((TaskView) child).getPrimaryTaskOffsetTranslationProperty()
+                    : mOrientationHandler.getPrimaryViewTranslate();
+            translationProperty.set(child,
                     totalTranslation * mOrientationHandler.getPrimaryTranslationDirectionFactor());
         }
         updateCurveProperties();
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index d94e623..b791d29 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -152,6 +152,58 @@
                 }
             };
 
+    private static final FloatProperty<TaskView> FILL_DISMISS_GAP_TRANSLATION_X =
+            new FloatProperty<TaskView>("fillDismissGapTranslationX") {
+                @Override
+                public void setValue(TaskView taskView, float v) {
+                    taskView.setFillDismissGapTranslationX(v);
+                }
+
+                @Override
+                public Float get(TaskView taskView) {
+                    return taskView.mFillDismissGapTranslationX;
+                }
+            };
+
+    private static final FloatProperty<TaskView> FILL_DISMISS_GAP_TRANSLATION_Y =
+            new FloatProperty<TaskView>("fillDismissGapTranslationY") {
+                @Override
+                public void setValue(TaskView taskView, float v) {
+                    taskView.setFillDismissGapTranslationY(v);
+                }
+
+                @Override
+                public Float get(TaskView taskView) {
+                    return taskView.mFillDismissGapTranslationY;
+                }
+            };
+
+    private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_X =
+            new FloatProperty<TaskView>("taskOffsetTranslationX") {
+                @Override
+                public void setValue(TaskView taskView, float v) {
+                    taskView.setTaskOffsetTranslationX(v);
+                }
+
+                @Override
+                public Float get(TaskView taskView) {
+                    return taskView.mTaskOffsetTranslationX;
+                }
+            };
+
+    private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_Y =
+            new FloatProperty<TaskView>("taskOffsetTranslationY") {
+                @Override
+                public void setValue(TaskView taskView, float v) {
+                    taskView.setTaskOffsetTranslationY(v);
+                }
+
+                @Override
+                public Float get(TaskView taskView) {
+                    return taskView.mTaskOffsetTranslationY;
+                }
+            };
+
     private final OnAttachStateChangeListener mTaskMenuStateListener =
             new OnAttachStateChangeListener() {
                 @Override
@@ -179,6 +231,13 @@
     private final FullscreenDrawParams mCurrentFullscreenParams;
     private final StatefulActivity mActivity;
 
+    // Various causes of changing primary translation, which we aggregate to setTranslationX/Y().
+    // TODO: We should do this for secondary translation properties as well.
+    private float mFillDismissGapTranslationX;
+    private float mFillDismissGapTranslationY;
+    private float mTaskOffsetTranslationX;
+    private float mTaskOffsetTranslationY;
+
     private ObjectAnimator mIconAndDimAnimator;
     private float mIconScaleAnimStartProgress = 0;
     private float mFocusTransitionProgress = 1;
@@ -601,6 +660,8 @@
 
     protected void resetViewTransforms() {
         setCurveScale(1);
+        mFillDismissGapTranslationX = mTaskOffsetTranslationX = 0f;
+        mFillDismissGapTranslationY = mTaskOffsetTranslationY = 0f;
         setTranslationX(0f);
         setTranslationY(0f);
         setTranslationZ(0);
@@ -745,6 +806,44 @@
         return mCurveScale;
     }
 
+    private void setFillDismissGapTranslationX(float x) {
+        mFillDismissGapTranslationX = x;
+        applyTranslationX();
+    }
+
+    private void setFillDismissGapTranslationY(float y) {
+        mFillDismissGapTranslationY = y;
+        applyTranslationY();
+    }
+
+    private void setTaskOffsetTranslationX(float x) {
+        mTaskOffsetTranslationX = x;
+        applyTranslationX();
+    }
+
+    private void setTaskOffsetTranslationY(float y) {
+        mTaskOffsetTranslationY = y;
+        applyTranslationY();
+    }
+
+    private void applyTranslationX() {
+        setTranslationX(mFillDismissGapTranslationX + mTaskOffsetTranslationX);
+    }
+
+    private void applyTranslationY() {
+        setTranslationY(mFillDismissGapTranslationY + mTaskOffsetTranslationY);
+    }
+
+    public FloatProperty<TaskView> getPrimaryFillDismissGapTranslationProperty() {
+        return getPagedOrientationHandler().getPrimaryValue(
+                FILL_DISMISS_GAP_TRANSLATION_X, FILL_DISMISS_GAP_TRANSLATION_Y);
+    }
+
+    public FloatProperty<TaskView> getPrimaryTaskOffsetTranslationProperty() {
+        return getPagedOrientationHandler().getPrimaryValue(
+                TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
+    }
+
     @Override
     public boolean hasOverlappingRendering() {
         // TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index b9e0f62..2e7e6e0 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage;
 import static com.android.launcher3.tapl.TestHelpers.getLauncherInMyProcess;
 import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_ACTIVITY_TIMEOUT;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_BROADCAST_TIMEOUT_SECS;
 import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT;
 import static com.android.launcher3.ui.AbstractLauncherUiTest.resolveSystemApp;
 import static com.android.launcher3.ui.AbstractLauncherUiTest.startAppFast;
@@ -55,7 +56,6 @@
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.FailureRewriterRule;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.quickstep.views.RecentsView;
 
@@ -66,6 +66,8 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.model.Statement;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -100,8 +102,7 @@
         }
 
         mOrderSensitiveRules = RuleChain
-                .outerRule(new FailureRewriterRule())
-                .around(new NavigationModeSwitchRule(mLauncher))
+                .outerRule(new NavigationModeSwitchRule(mLauncher))
                 .around(new FailureWatcher(mDevice));
 
         mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
@@ -112,11 +113,16 @@
             @Override
             public void evaluate() throws Throwable {
                 TestCommandReceiver.callCommand(TestCommandReceiver.ENABLE_TEST_LAUNCHER);
+                OverviewUpdateHandler updateHandler =
+                        MAIN_EXECUTOR.submit(OverviewUpdateHandler::new).get();
                 UiDevice.getInstance(getInstrumentation()).executeShellCommand(
                         getLauncherCommand(mOtherLauncherActivity));
+                updateHandler.mChangeCounter
+                        .await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS);
                 try {
                     base.evaluate();
                 } finally {
+                    MAIN_EXECUTOR.submit(updateHandler::destroy).get();
                     TestCommandReceiver.callCommand(TestCommandReceiver.DISABLE_TEST_LAUNCHER);
                     UiDevice.getInstance(getInstrumentation()).executeShellCommand(
                             getLauncherCommand(getLauncherInMyProcess()));
@@ -213,7 +219,7 @@
         OverviewTask task = overview.getCurrentTask();
         assertNotNull("overview.getCurrentTask() returned null (1)", task);
         assertNotNull("OverviewTask.open returned null", task.open());
-        assertTrue("Test activity didn't open from Overview", mDevice.wait(Until.hasObject(
+        assertTrue("Test activity didn't open from Overview", TestHelpers.wait(Until.hasObject(
                 By.pkg(getAppPackageName()).text("TestActivity2")),
                 DEFAULT_UI_TIMEOUT));
 
@@ -230,7 +236,7 @@
 
         // Test dismissing all tasks.
         pressHomeAndGoToOverview().dismissAllTasks();
-        assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
+        assertTrue("Fallback Launcher not visible", TestHelpers.wait(Until.hasObject(By.pkg(
                 mOtherLauncherActivity.packageName)), WAIT_TIME_MS));
     }
 
@@ -241,4 +247,30 @@
     private int getTaskCount(RecentsActivity recents) {
         return recents.<RecentsView>getOverviewPanel().getTaskViewCount();
     }
+
+    private class OverviewUpdateHandler {
+
+        final RecentsAnimationDeviceState mRads;
+        final OverviewComponentObserver mObserver;
+        final CountDownLatch mChangeCounter;
+
+        OverviewUpdateHandler() {
+            Context ctx = getInstrumentation().getTargetContext();
+            mRads = new RecentsAnimationDeviceState(ctx);
+            mObserver = new OverviewComponentObserver(ctx, mRads);
+            mChangeCounter = new CountDownLatch(1);
+            if (mObserver.getHomeIntent().getComponent()
+                    .getPackageName().equals(mOtherLauncherActivity.packageName)) {
+                // Home already same
+                mChangeCounter.countDown();
+            } else {
+                mObserver.setOverviewChangeListener(b -> mChangeCounter.countDown());
+            }
+        }
+
+        void destroy() {
+            mObserver.onDestroy();
+            mRads.destroy();
+        }
+    }
 }
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java
index f019a20..fdddab4 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java
@@ -18,6 +18,8 @@
 import static android.view.View.MeasureSpec.EXACTLY;
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 
+import static com.android.launcher3.Utilities.createHomeIntent;
+
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
@@ -46,10 +48,7 @@
      */
     public static String getLauncherClassName() {
         Context context = RuntimeEnvironment.application;
-        Intent homeIntent = new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_HOME)
-                .setPackage(context.getPackageName())
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        Intent homeIntent = createHomeIntent().setPackage(context.getPackageName());
 
         List<ResolveInfo> launchers = context.getPackageManager()
                 .queryIntentActivities(homeIntent, 0);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5b55c4b..0274775 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1596,6 +1596,7 @@
 
         mOverlayManager.onActivityDestroyed(this);
         mAppTransitionManager.unregisterRemoteAnimations();
+        mAppTransitionManager.unregisterRemoteTransitions();
         mUserChangedCallbackCloseable.close();
         mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
         mLiveSearchManager.stop();
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
index 24e0d14..ac3ad9f 100644
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ b/src/com/android/launcher3/LauncherAppTransitionManager.java
@@ -67,4 +67,18 @@
     public void unregisterRemoteAnimations() {
         // Do nothing
     }
+
+    /**
+     * Registers remote transitions for certain system transitions.
+     */
+    public void registerRemoteTransitions() {
+        // Do nothing
+    }
+
+    /**
+     * Unregisters all remote transitions.
+     */
+    public void unregisterRemoteTransitions() {
+        // Do nothing
+    }
 }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index df5d234..8066aa6 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -461,6 +461,15 @@
     }
 
     /**
+     * Returns an intent for starting the default home activity
+     */
+    public static Intent createHomeIntent() {
+        return new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    }
+
+    /**
      * Wraps a message with a TTS span, so that a different message is spoken than
      * what is getting displayed.
      * @param msg original message
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index aa056a0..f926086 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -59,6 +59,11 @@
             Interpolator interpolator);
 
     /**
+     * Called when activity is destroyed. Used to close search system services
+     */
+    default void destroy(){}
+
+    /**
      * Returns true if the QSB should be visible for the given set of visible elements
      */
     default boolean isQsbVisible(int visibleElements) {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 2455706..1c5c222 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -98,9 +98,8 @@
     public static final BooleanFlag ENABLE_DEVICE_SEARCH = getDebugFlag(
             "ENABLE_DEVICE_SEARCH", false, "Allows on device search in all apps");
 
-    public static final BooleanFlag SEARCH_TARGET_LEGACY = getDebugFlag(
-            "SEARCH_TARGET_LEGACY", true,
-            "Use SearchTarget provided by plugin lib (only during migration)");
+    public static final BooleanFlag USE_SEARCH_API = getDebugFlag(
+            "USE_SEARCH_API", true, "Use SearchUIManager api for device search");
 
     public static final BooleanFlag DISABLE_INITIAL_IME_IN_ALLAPPS = getDebugFlag(
             "DISABLE_INITIAL_IME_IN_ALLAPPS", false, "Disable default IME state in all apps");
@@ -215,6 +214,9 @@
             "ENABLE_APP_PREDICTIONS_WHILE_VISIBLE", true, "Allows app "
             + "predictions to be updated while they are visible to the user.");
 
+    public static final BooleanFlag ENABLE_TASKBAR = new DeviceFlag(
+            "ENABLE_TASKBAR", false, "Allows a system Taskbar to be shown on larger devices.");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index c57c3e4..d4fa278 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -26,7 +26,6 @@
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.os.LocaleList;
-import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
 
@@ -162,7 +161,7 @@
     /** Updates the given PackageInstallInfo's associated AppInfo's installation info. */
     public List<AppInfo> updatePromiseInstallInfo(PackageInstallInfo installInfo) {
         List<AppInfo> updatedAppInfos = new ArrayList<>();
-        UserHandle user = Process.myUserHandle();
+        UserHandle user = installInfo.user;
         for (int i = data.size() - 1; i >= 0; i--) {
             final AppInfo appInfo = data.get(i);
             final ComponentName tgtComp = appInfo.getTargetComponent();
@@ -177,7 +176,8 @@
                     appInfo.setProgressLevel(installInfo);
 
                     updatedAppInfos.add(appInfo);
-                } else if (installInfo.state == PackageInstallInfo.STATUS_FAILED) {
+                } else if (installInfo.state == PackageInstallInfo.STATUS_FAILED
+                        && !appInfo.isAppStartable()) {
                     removeApp(i);
                 }
             }
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index 5e48a0f..d09bf81 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -104,8 +104,10 @@
     @WorkerThread
     private void addToQueue(PendingInstallShortcutInfo info) {
         ensureQueueLoaded();
-        mItems.add(info);
-        mStorage.write(mContext, mItems);
+        if (!mItems.contains(info)) {
+            mItems.add(info);
+            mStorage.write(mContext, mItems);
+        }
     }
 
     @WorkerThread
@@ -303,6 +305,33 @@
             }
             return null;
         }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof PendingInstallShortcutInfo) {
+                PendingInstallShortcutInfo other = (PendingInstallShortcutInfo) obj;
+
+                boolean userMatches = user.equals(other.user);
+                boolean itemTypeMatches = itemType == other.itemType;
+                boolean intentMatches = intent.toUri(0).equals(other.intent.toUri(0));
+                boolean shortcutInfoMatches = shortcutInfo == null
+                        ? other.shortcutInfo == null
+                        : other.shortcutInfo != null
+                            && shortcutInfo.getId().equals(other.shortcutInfo.getId())
+                            && shortcutInfo.getPackage().equals(other.shortcutInfo.getPackage());
+                boolean providerInfoMatches = providerInfo == null
+                        ? other.providerInfo == null
+                        : other.providerInfo != null
+                            && providerInfo.provider.equals(other.providerInfo.provider);
+
+                return userMatches
+                        && itemTypeMatches
+                        && intentMatches
+                        && shortcutInfoMatches
+                        && providerInfoMatches;
+            }
+            return false;
+        }
     }
 
     private static String getIntentPackage(Intent intent) {
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index ecf4f36..2da06e9 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -51,7 +51,7 @@
     public static final int REQUEST_ROTATE = 1;
     public static final int REQUEST_LOCK = 2;
 
-    private final Activity mActivity;
+    private Activity mActivity;
     private final SharedPreferences mSharedPrefs;
 
     private boolean mIgnoreAutoRotateSettings;
@@ -76,7 +76,8 @@
     private boolean mInitialized;
     private boolean mDestroyed;
 
-    private int mLastActivityFlags = -1;
+    // Initialize mLastActivityFlags to a value not used by SCREEN_ORIENTATION flags
+    private int mLastActivityFlags = -999;
 
     public RotationHelper(Activity activity) {
         mActivity = activity;
@@ -95,6 +96,7 @@
 
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+        if (mDestroyed) return;
         boolean wasRotationEnabled = mHomeRotationEnabled;
         mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                 getAllowRotationDefaultValue());
@@ -141,6 +143,7 @@
     public void destroy() {
         if (!mDestroyed) {
             mDestroyed = true;
+            mActivity = null;
             if (mSharedPrefs != null) {
                 mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
             }
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index 539768e..14df0f3 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -34,7 +34,6 @@
 
     public static final String HOME_BOUNCE_SEEN = "launcher.apps_view_shown";
     public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
-    public static final String SHELF_BOUNCE_COUNT = "launcher.shelf_bounce_count";
     public static final String HOTSEAT_DISCOVERY_TIP_COUNT = "launcher.hotseat_discovery_tip_count";
     public static final String HOTSEAT_LONGPRESS_TIP_SEEN = "launcher.hotseat_longpress_tip_seen";
 
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index fab0bd4..4e82336 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -161,6 +161,11 @@
         float scaleY = rect.height() / minSize;
         float scale = Math.max(1f, Math.min(scaleX, scaleY));
 
+        if (Float.isNaN(scale)) {
+            // Views are no longer laid out, do not update.
+            return;
+        }
+
         update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale,
                 minSize, lp, isVerticalBarLayout, dp);
 
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 37dd4d2..b3c1240 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -63,7 +63,6 @@
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.FailureRewriterRule;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.launcher3.util.rule.LauncherActivityRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
@@ -100,6 +99,7 @@
     private static final String TAG = "AbstractLauncherUiTest";
 
     private static String sStrictmodeDetectedActivityLeak;
+    private static boolean sDumpWasGenerated = false;
     private static boolean sActivityLeakReported;
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
     protected static final ActivityLeakTracker ACTIVITY_LEAK_TRACKER = new ActivityLeakTracker();
@@ -151,10 +151,18 @@
     }
 
     public static String dumpHprofData() {
+        if (sDumpWasGenerated) return "dump has already been generated by another test";
         try {
             final String fileName = getInstrumentation().getTargetContext().getFilesDir().getPath()
                     + "/ActivityLeakHeapDump.hprof";
-            Debug.dumpHprofData(fileName);
+            if (TestHelpers.isInLauncherProcess()) {
+                Debug.dumpHprofData(fileName);
+            } else {
+                final UiDevice device = UiDevice.getInstance(getInstrumentation());
+                device.executeShellCommand(
+                        "am dumpheap " + device.getLauncherPackageName() + " " + fileName);
+            }
+            sDumpWasGenerated = true;
             return "memory dump filename: " + fileName;
         } catch (Throwable e) {
             Log.e(TAG, "dumpHprofData failed", e);
@@ -224,9 +232,8 @@
     }
 
     @Rule
-    public TestRule mOrderSensitiveRules = RuleChain.
-            outerRule(new FailureRewriterRule())
-            .around(new TestStabilityRule())
+    public TestRule mOrderSensitiveRules = RuleChain
+            .outerRule(new TestStabilityRule())
             .around(mActivityMonitor)
             .around(getRulesInsideActivityMonitor());
 
@@ -237,7 +244,7 @@
     @Before
     public void setUp() throws Exception {
         Assert.assertTrue("Keyguard is visible",
-                mDevice.wait(
+                TestHelpers.wait(
                         Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000));
 
         final String launcherPackageName = mDevice.getLauncherPackageName();
@@ -470,8 +477,7 @@
         }
         getInstrumentation().getTargetContext().startActivity(intent);
         assertTrue("App didn't start: " + selector,
-                UiDevice.getInstance(getInstrumentation())
-                        .wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT));
+                TestHelpers.wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT));
     }
 
     public static ActivityInfo resolveSystemAppInfo(String category) {
diff --git a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
deleted file mode 100644
index 77546de..0000000
--- a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util.rule;
-
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import android.os.SystemClock;
-
-import androidx.test.uiautomator.UiDevice;
-
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.regex.Pattern;
-
-class FailureInvestigator {
-    private static boolean matches(String regex, CharSequence string) {
-        return Pattern.compile(regex).matcher(string).find();
-    }
-
-    static class LogcatMatch {
-        String logcatPattern;
-        int bug;
-
-        LogcatMatch(String logcatPattern, int bug) {
-            this.logcatPattern = logcatPattern;
-            this.bug = bug;
-        }
-    }
-
-    static class ExceptionMatch {
-        String exceptionPattern;
-        LogcatMatch[] logcatMatches;
-
-        ExceptionMatch(String exceptionPattern, LogcatMatch[] logcatMatches) {
-            this.exceptionPattern = exceptionPattern;
-            this.logcatMatches = logcatMatches;
-        }
-    }
-
-    private static final ExceptionMatch[] EXCEPTION_MATCHES = {
-            new ExceptionMatch(
-                    "java.lang.AssertionError: http://go/tapl : Tests are broken by a "
-                            + "non-Launcher system error: (Phone is locked|Screen is empty)",
-                    new LogcatMatch[]{
-                            new LogcatMatch(
-                                    "BroadcastQueue: Can't deliver broadcast to com.android"
-                                            + ".systemui.*Crashing it",
-                                    147845913),
-                            new LogcatMatch(
-                                    "Attempt to invoke virtual method 'boolean android\\"
-                                            + ".graphics\\.Bitmap\\.isRecycled\\(\\)' on a null "
-                                            + "object reference",
-                                    148424291),
-                            new LogcatMatch(
-                                    "java\\.lang\\.IllegalArgumentException\\: Ranking map "
-                                            + "doesn't contain key",
-                                    148570537),
-                    }),
-            new ExceptionMatch("Launcher didn't initialize",
-                    new LogcatMatch[]{
-                            new LogcatMatch(
-                                    "ActivityManager: Reason: executing service com.google"
-                                            + ".android.apps.nexuslauncher/com.android.launcher3"
-                                            + ".notification.NotificationListener",
-                                    148238677),
-                    }),
-    };
-
-    static int getBugForFailure(CharSequence exception) {
-        if ("com.google.android.setupwizard".equals(
-                UiDevice.getInstance(getInstrumentation()).getLauncherPackageName())) {
-            return 145935261;
-        }
-
-        final String logSinceBoot;
-        try {
-            final String systemBootTime =
-                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(
-                            new Date(System.currentTimeMillis() - SystemClock.elapsedRealtime()));
-
-            logSinceBoot =
-                    UiDevice.getInstance(getInstrumentation())
-                            .executeShellCommand("logcat -d -t " + systemBootTime.replace(" ", ""));
-        } catch (IOException | OutOfMemoryError e) {
-            return 0;
-        }
-
-        if (matches("android\\:\\:uirenderer\\:\\:renderthread\\:\\:EglManager\\:\\:swapBuffers",
-                logSinceBoot)) {
-            return 148529608;
-        }
-
-        for (ExceptionMatch exceptionMatch : EXCEPTION_MATCHES) {
-            if (matches(exceptionMatch.exceptionPattern, exception)) {
-                for (LogcatMatch logcatMatch : exceptionMatch.logcatMatches) {
-                    if (matches(logcatMatch.logcatPattern, logSinceBoot)) {
-                        return logcatMatch.bug;
-                    }
-                }
-                break;
-            }
-        }
-
-        return 0;
-    }
-}
diff --git a/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java b/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java
deleted file mode 100644
index 99ddee4..0000000
--- a/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util.rule;
-
-import android.util.Log;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-public class FailureRewriterRule implements TestRule {
-    private static final String TAG = "FailureRewriter";
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                try {
-                    base.evaluate();
-                } catch (Throwable e) {
-                    final int bug = FailureInvestigator.getBugForFailure(e.toString());
-                    if (bug == 0) throw e;
-
-                    Log.e(TAG, "Known bug found for the original failure "
-                            + android.util.Log.getStackTraceString(e));
-                    throw new AssertionError(
-                            "Detected a failure that matches a known bug b/" + bug);
-                }
-            }
-        };
-    }
-}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 093c024..3fc83ff 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -68,8 +68,7 @@
 
         mLauncher.assertTrue(
                 "App didn't start: " + label,
-                mLauncher.getDevice().wait(Until.hasObject(selector),
-                        LauncherInstrumentation.WAIT_TIME_MS));
+                TestHelpers.wait(Until.hasObject(selector), LauncherInstrumentation.WAIT_TIME_MS));
         return new Background(mLauncher);
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index e2a442d..0c8f610 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -933,7 +933,7 @@
 
     @NonNull
     UiObject2 waitForAndroidObject(String resId) {
-        final UiObject2 object = mDevice.wait(
+        final UiObject2 object = TestHelpers.wait(
                 Until.findObject(By.res(ANDROID_PACKAGE, resId)), WAIT_TIME_MS);
         assertNotNull("Can't find a android object with id: " + resId, object);
         return object;
diff --git a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
index b8791e8..7f6062f 100644
--- a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
+++ b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
@@ -27,6 +27,11 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.DropBoxManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.uiautomator.SearchCondition;
+import androidx.test.uiautomator.UiDevice;
 
 import org.junit.Assert;
 
@@ -35,6 +40,7 @@
 
 public class TestHelpers {
 
+    private static final String TAG = "Tapl";
     private static Boolean sIsInLauncherProcess;
 
     public static boolean isInLauncherProcess() {
@@ -154,4 +160,12 @@
             return null;
         }
     }
+
+    public static <R> R wait(SearchCondition<R> condition, long timeout) {
+        Log.d(TAG,
+                "TestHelpers.wait, condition=" + timeout + ", time=" + SystemClock.uptimeMillis());
+        final R result = UiDevice.getInstance(getInstrumentation()).wait(condition, timeout);
+        Log.d(TAG, "TestHelpers.wait, result=" + result + ", time=" + SystemClock.uptimeMillis());
+        return result;
+    }
 }