Merge "Call into shell for recent tasks"
diff --git a/Android.bp b/Android.bp
index ac7e136..bab02b8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -258,9 +258,9 @@
         "go/quickstep/res",
     ],
     static_libs: [
-        "Launcher3CommonDepsLib",
         "QuickstepResLib",
         "androidx.room_room-runtime",
+        "Launcher3CommonDepsLib",
     ],
     plugins: ["androidx.room_room-compiler-plugin"],
     manifest: "quickstep/AndroidManifest-launcher.xml",
@@ -280,16 +280,15 @@
     srcs: [
         ":launcher-src-no-build-config",
     ],
-    resource_dirs: [
-        "quickstep/res",
-    ],
+    resource_dirs: [],
     libs: [
         "framework-statsd",
     ],
     static_libs: [
+        "QuickstepResLib",
         "SystemUI-statsd",
         "SystemUISharedLib",
-        "Launcher3CommonDepsLib"
+        "Launcher3CommonDepsLib",
     ],
     manifest: "quickstep/AndroidManifest.xml",
     platform_apis: true,
diff --git a/quickstep/res/drawable/task_menu_item_bg.xml b/quickstep/res/drawable/task_menu_item_bg.xml
index b6a8b90..16c13eb 100644
--- a/quickstep/res/drawable/task_menu_item_bg.xml
+++ b/quickstep/res/drawable/task_menu_item_bg.xml
@@ -15,7 +15,8 @@
      limitations under the License.
 -->
 
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?android:attr/colorPrimary"/>
-    <corners android:radius="@dimen/task_menu_item_corner_radius"/>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <solid android:color="?androidprv:attr/colorSurface" />
+    <corners android:radius="@dimen/task_menu_item_corner_radius" />
 </shape>
diff --git a/quickstep/res/layout/task_menu_with_arrow.xml b/quickstep/res/layout/task_menu_with_arrow.xml
new file mode 100644
index 0000000..38573fd
--- /dev/null
+++ b/quickstep/res/layout/task_menu_with_arrow.xml
@@ -0,0 +1,33 @@
+<?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.quickstep.views.TaskMenuViewWithArrow
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:animateLayoutChanges="true"
+    android:background="@drawable/task_menu_bg"
+    android:orientation="vertical"
+    android:visibility="invisible">
+
+    <LinearLayout
+        android:id="@+id/menu_option_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:showDividers="middle" />
+
+</com.android.quickstep.views.TaskMenuViewWithArrow>
\ No newline at end of file
diff --git a/quickstep/res/layout/task_view_menu_option.xml b/quickstep/res/layout/task_view_menu_option.xml
index 5978b97..8a8fc36 100644
--- a/quickstep/res/layout/task_view_menu_option.xml
+++ b/quickstep/res/layout/task_view_menu_option.xml
@@ -18,7 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:orientation="vertical"
+    android:orientation="horizontal"
     android:paddingTop="@dimen/task_card_menu_option_vertical_padding"
     android:paddingBottom="@dimen/task_card_menu_option_vertical_padding"
     android:background="@drawable/task_menu_item_bg"
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 98d43f1..8649a1d 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -27,7 +27,7 @@
     <dimen name="task_menu_corner_radius">22dp</dimen>
     <dimen name="task_menu_item_corner_radius">4dp</dimen>
     <dimen name="task_menu_spacing">2dp</dimen>
-    <dimen name="task_menu_width_grid">200dp</dimen>
+    <dimen name="task_menu_width_grid">234dp</dimen>
     <dimen name="overview_proactive_row_height">48dp</dimen>
     <dimen name="overview_proactive_row_bottom_margin">16dp</dimen>
 
@@ -90,7 +90,7 @@
     <dimen name="task_menu_vertical_padding">8dp</dimen>
     <dimen name="task_card_margin">8dp</dimen>
     <dimen name="task_card_menu_shadow_height">3dp</dimen>
-    <dimen name="task_menu_option_start_margin">12dp</dimen>
+    <dimen name="task_menu_option_start_margin">16dp</dimen>
     <!-- Copied from framework resource:
        docked_stack_divider_thickness - 2 * docked_stack_divider_insets -->
     <dimen name="multi_window_task_divider_size">10dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 5fc79f0..35151f1 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -17,11 +17,8 @@
 
 import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
 import android.os.Build;
 import android.os.CancellationSignal;
-import android.os.Handler;
 
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
@@ -78,11 +75,4 @@
         mRemoteAnimationProvider = null;
         super.unregister();
     }
-
-    @Override
-    public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
-            Context context, Handler handler, long duration) {
-        mRemoteAnimationProvider = animProvider;
-        super.registerAndStartActivity(intent, animProvider, context, handler, duration);
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 7e5bda5..2ccec44 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -285,7 +285,8 @@
         long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION
                 - STATUS_BAR_TRANSITION_PRE_DELAY;
         RemoteAnimationAdapterCompat adapterCompat =
-                new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay);
+                new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay,
+                        mLauncher.getIApplicationThread());
         return new ActivityOptionsWrapper(
                 ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback);
     }
@@ -1081,7 +1082,8 @@
                     new RemoteAnimationAdapterCompat(
                             new LauncherAnimationRunner(mHandler, mWallpaperOpenRunner,
                                     false /* startAtFrontOfQueue */),
-                            CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
+                            CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */,
+                            mLauncher.getIApplicationThread()));
 
             if (KEYGUARD_ANIMATION.get()) {
                 mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */);
@@ -1091,7 +1093,8 @@
                                 new LauncherAnimationRunner(
                                         mHandler, mKeyguardGoingAwayRunner,
                                         true /* startAtFrontOfQueue */),
-                                CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
+                                CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */,
+                                mLauncher.getIApplicationThread()));
             }
 
             new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
@@ -1109,7 +1112,7 @@
             mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */);
             mLauncherOpenTransition = RemoteAnimationAdapterCompat.buildRemoteTransition(
                     new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner,
-                            false /* startAtFrontOfQueue */));
+                            false /* startAtFrontOfQueue */), mLauncher.getIApplicationThread());
             mLauncherOpenTransition.addHomeOpenCheck(mLauncher.getComponentName());
             SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition);
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index d35b0f6..4b6dacd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -234,14 +234,6 @@
         mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
     }
 
-    public void onDestroy() {
-        mPropertyHolders.clear();
-        mControllers.rotationButtonController.unregisterListeners();
-        if (mFloatingRotationButton != null) {
-            mFloatingRotationButton.hide();
-        }
-    }
-
     private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
             TaskbarNavButtonController navButtonController) {
 
@@ -438,6 +430,14 @@
         return mFloatingRotationButtonBounds.contains((int) ev.getX(), (int) ev.getY());
     }
 
+    public void onDestroy() {
+        mPropertyHolders.clear();
+        mControllers.rotationButtonController.unregisterListeners();
+        if (mFloatingRotationButton != null) {
+            mFloatingRotationButton.hide();
+        }
+    }
+
     private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
         @Override
         public void onVisibilityChanged(boolean isVisible) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 72d9d5b..5bc454e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
+import static com.android.launcher3.testing.TestProtocol.TASKBAR_WINDOW_CRASH;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
@@ -60,6 +61,7 @@
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.SettingsCache;
@@ -200,6 +202,9 @@
         updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
 
         mWindowManager.addView(mDragLayer, mWindowLayoutParams);
+        if (TestProtocol.sDebugTracing) {
+            Log.e(TASKBAR_WINDOW_CRASH, "Adding taskbar window");
+        }
     }
 
     public boolean isThreeButtonNav() {
@@ -330,6 +335,9 @@
         setUIController(TaskbarUIController.DEFAULT);
         mControllers.onDestroy();
         mWindowManager.removeViewImmediate(mDragLayer);
+        if (TestProtocol.sDebugTracing) {
+            Log.e(TASKBAR_WINDOW_CRASH, "Removing taskbar window");
+        }
     }
 
     public void updateSysuiStateFlags(int systemUiStateFlags, boolean fromInit) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 089c265..53feaad 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -17,6 +17,8 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+
+import static com.android.launcher3.testing.TestProtocol.TASKBAR_WINDOW_CRASH;
 import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
@@ -29,6 +31,7 @@
 import android.hardware.display.DisplayManager;
 import android.net.Uri;
 import android.provider.Settings;
+import android.util.Log;
 import android.view.Display;
 
 import androidx.annotation.NonNull;
@@ -39,6 +42,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.SettingsCache;
@@ -203,6 +207,10 @@
     }
 
     private void recreateTaskbar() {
+        if (TestProtocol.sDebugTracing) {
+            Log.e(TASKBAR_WINDOW_CRASH, "Recreating taskbar: mTaskbarActivityContext="
+                    + mTaskbarActivityContext);
+        }
         destroyExistingTaskbar();
 
         DeviceProfile dp =
@@ -225,6 +233,9 @@
             mTaskbarActivityContext.setUIController(
                     createTaskbarUIControllerForActivity(mActivity));
         }
+        if (TestProtocol.sDebugTracing) {
+            Log.e(TASKBAR_WINDOW_CRASH, "Finished recreating taskbar");
+        }
     }
 
     public void onSystemUiFlagsChanged(int systemUiStateFlags) {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index ad7e4df..09a0b7d 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -245,7 +245,7 @@
         RemoteAnimationAdapterCompat adapterCompat = new RemoteAnimationAdapterCompat(
                 wrapper, RECENTS_LAUNCH_DURATION,
                 RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION
-                        - STATUS_BAR_TRANSITION_PRE_DELAY);
+                        - STATUS_BAR_TRANSITION_PRE_DELAY, getIApplicationThread());
         final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper(
                 ActivityOptionsCompat.makeRemoteAnimation(adapterCompat),
                 onEndCallback);
@@ -394,7 +394,8 @@
         LauncherAnimationRunner runner = new LauncherAnimationRunner(
                 getMainThreadHandler(), mAnimationToHomeFactory, true);
         RemoteAnimationAdapterCompat adapterCompat =
-                new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0);
+                new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0,
+                        getIApplicationThread());
         startActivity(createHomeIntent(),
                 ActivityOptionsCompat.makeRemoteAnimation(adapterCompat).toBundle());
     }
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 9cd206a..2570c84 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -39,6 +39,7 @@
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -457,6 +458,8 @@
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call handleImageBundleAsScreenshot");
             }
+        } else if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SCREENSHOT, "sysuiproxy, no proxy available");
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 4b89981..258a0c2 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -178,7 +178,8 @@
 
         if (ENABLE_SHELL_TRANSITIONS) {
             RemoteTransitionCompat transition = new RemoteTransitionCompat(mCallbacks,
-                    mController != null ? mController.getController() : null);
+                    mController != null ? mController.getController() : null,
+                    mCtx.getIApplicationThread());
             Bundle options = ActivityOptionsCompat.makeRemoteTransition(transition)
                     .setTransientLaunch().toBundle();
             mCtx.startActivity(intent, options);
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index b9c0657..502ab68 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -22,6 +22,7 @@
 
 import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.testing.TestProtocol.TASKBAR_WINDOW_CRASH;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -346,6 +347,9 @@
     @Override
     public void onCreate() {
         super.onCreate();
+        if (TestProtocol.sDebugTracing) {
+            Log.e(TASKBAR_WINDOW_CRASH, "TIS created");
+        }
         // Initialize anything here that is needed in direct boot mode.
         // Everything else should be initialized in onUserUnlocked() below.
         mMainChoreographer = Choreographer.getInstance();
@@ -507,6 +511,9 @@
     @Override
     public void onDestroy() {
         Log.d(TAG, "Touch service destroyed: user=" + getUserId());
+        if (TestProtocol.sDebugTracing) {
+            Log.e(TASKBAR_WINDOW_CRASH, "TIS destroyed");
+        }
         sIsInitialized = false;
         if (mDeviceState.isUserUnlocked()) {
             mInputConsumer.unregisterInputConsumer();
diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
index b9879ab..aeec36f 100644
--- a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
+++ b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
@@ -15,11 +15,6 @@
  */
 package com.android.quickstep.util;
 
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.util.ActivityTracker;
 import com.android.launcher3.util.ActivityTracker.SchedulerCallback;
@@ -75,17 +70,4 @@
         mIsRegistered = false;
         mOnInitListener = null;
     }
-
-    /**
-     * Starts the given intent with the provided animation. Unlike {@link #register(Intent)}, this
-     * method will not call {@link #init} if the activity already exists, it will only call it when
-     * we get handleIntent() for the provided intent that we're starting.
-     */
-    public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
-            Context context, Handler handler, long duration) {
-        register();
-
-        Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
-        context.startActivity(new Intent(intent), options);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index 51a9915..2858cbd 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -49,6 +49,7 @@
 
 import com.android.internal.app.ChooserActivity;
 import com.android.launcher3.BuildConfig;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.quickstep.SystemUiProxy;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.utilities.BitmapUtil;
@@ -77,6 +78,9 @@
     public static void saveScreenshot(SystemUiProxy systemUiProxy, Bitmap screenshot,
             Rect screenshotBounds,
             Insets visibleInsets, Task.TaskKey task) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SCREENSHOT, "image action utils calling into sysuiproxy");
+        }
         systemUiProxy.handleImageBundleAsScreenshot(BitmapUtil.hardwareBitmapToBundle(screenshot),
                 screenshotBounds, visibleInsets, task);
     }
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 98dbd47..ee82ae6 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -16,32 +16,14 @@
 package com.android.quickstep.util;
 
 import android.animation.AnimatorSet;
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.os.Handler;
 
-import com.android.launcher3.LauncherAnimationRunner;
-import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
-import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 public abstract class RemoteAnimationProvider {
 
-    RemoteAnimationFactory mAnimationRunner;
-
     public abstract AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets);
 
-    ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
-        mAnimationRunner = (transit, appTargets, wallpaperTargets, nonApps, result) ->
-                result.setAnimation(createWindowAnimation(appTargets, wallpaperTargets), context);
-        final LauncherAnimationRunner wrapper = new LauncherAnimationRunner(
-                handler, mAnimationRunner, false /* startAtFrontOfQueue */);
-        return ActivityOptionsCompat.makeRemoteAnimation(
-                new RemoteAnimationAdapterCompat(wrapper, duration, 0));
-    }
-
     /**
      * @return the target with the lowest opaque layer for a certain app animation, or null.
      */
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 1dae2c8..4c300ec 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -94,7 +94,8 @@
                     new RemoteSplitLaunchTransitionRunner(task1, task2);
             mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],
                     null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
-                    new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR));
+                    new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR,
+                            ActivityThread.currentActivityThread().getApplicationThread()));
         } else {
             RemoteSplitLaunchAnimationRunner animationRunner =
                     new RemoteSplitLaunchAnimationRunner(task1, task2, callback);
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 734c844..977c696 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -99,6 +100,7 @@
     private boolean mLayoutValid = false;
     private int mOrientationStateId;
     private StagedSplitBounds mStagedSplitBounds;
+    private boolean mDrawsBelowRecents;
 
     public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
         mContext = context;
@@ -198,6 +200,10 @@
         recentsViewScroll.value = scroll;
     }
 
+    public void setDrawsBelowRecents(boolean drawsBelowRecents) {
+        mDrawsBelowRecents = drawsBelowRecents;
+    }
+
     /**
      * Adds animation for all the components corresponding to transition from an app to overview.
      */
@@ -351,6 +357,12 @@
         builder.withMatrix(mMatrix)
                 .withWindowCrop(mTmpCropRect)
                 .withCornerRadius(getCurrentCornerRadius());
+
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
+            // When relativeLayer = 0, it reverts the surfaces back to the original order.
+            builder.withRelativeLayerTo(params.getRecentsSurface(),
+                    mDrawsBelowRecents ? Integer.MIN_VALUE : 0);
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 16c3c7f..5a8cbc1 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -16,12 +16,11 @@
 
 package com.android.quickstep.views;
 
-import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
-
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.FrameLayout;
@@ -32,7 +31,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.SysUINavigationMode;
@@ -113,6 +112,11 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         findViewById(R.id.action_screenshot).setOnClickListener(this);
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SCREENSHOT, "Inflated OverviewActionsView and added screenshot"
+                    + " listener.");
+        }
+
         mSplitButton = findViewById(R.id.action_split);
         mSplitButton.setOnClickListener(this);
     }
@@ -128,6 +132,11 @@
 
     @Override
     public void onClick(View view) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SCREENSHOT, "OverviewActionsView - onClick"
+                    + " callbacks: " + mCallbacks + "  view id: " + view.getId() + " "
+                    + " is screenshot? " + (view.getId() == R.id.action_screenshot));
+        }
         if (mCallbacks == null) {
             return;
         }
@@ -149,7 +158,7 @@
     public void setInsets(Rect insets) {
         mInsets.set(insets);
         updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
-        updatePaddingAndTranslations();
+        updateHorizontalPadding();
     }
 
     public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
@@ -192,37 +201,8 @@
         return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA);
     }
 
-    /**
-     * Aligns OverviewActionsView vertically with and offsets horizontal position based on
-     * 3 button nav container in taskbar.
-     */
-    private void updatePaddingAndTranslations() {
-        boolean alignFor3ButtonTaskbar = mDp.isTaskbarPresent &&
-                SysUINavigationMode.getMode(getContext()) == THREE_BUTTONS;
-        if (alignFor3ButtonTaskbar) {
-            // Add extra horizontal spacing
-            int additionalPadding = ApiWrapper.getHotseatEndOffset(getContext());
-            if (isLayoutRtl()) {
-                setPadding(mInsets.left + additionalPadding, 0, mInsets.right, 0);
-            } else {
-                setPadding(mInsets.left, 0, mInsets.right + additionalPadding, 0);
-            }
-
-            // Align vertically, using taskbar height + mDp.taskbarOffsetY() to guestimate
-            // where the button nav top is
-            View startActionView = findViewById(R.id.action_buttons);
-            int marginBottom = getOverviewActionsBottomMarginPx(
-                    SysUINavigationMode.getMode(getContext()), mDp);
-            int actionsTop = (mDp.heightPx - marginBottom - mInsets.bottom);
-            int navTop = mDp.heightPx - (mDp.taskbarSize + mDp.getTaskbarOffsetY());
-            int transY = navTop - actionsTop
-                    + ((mDp.taskbarSize - startActionView.getHeight()) / 2);
-            setTranslationY(transY);
-        } else {
-            setPadding(mInsets.left, 0, mInsets.right, 0);
-            setTranslationX(0);
-            setTranslationY(0);
-        }
+    private void updateHorizontalPadding() {
+        setPadding(mInsets.left, 0, mInsets.right, 0);
     }
 
     /** Updates vertical margins for different navigation mode or configuration changes. */
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 37052e5..b5238c6 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1095,7 +1095,7 @@
             for (int i = 0; i < taskCount; i++) {
                 View v = getTaskViewAt(i);
                 if (!(v instanceof GroupedTaskView)) {
-                    continue;
+                    return;
                 }
                 GroupedTaskView gtv = (GroupedTaskView) v;
                 gtv.onTaskListVisibilityChanged(false);
@@ -1914,8 +1914,10 @@
             }
         }
         setEnableDrawingLiveTile(false);
-        runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
-                .setTargetSet(null));
+        runActionOnRemoteHandles(remoteTargetHandle -> {
+            remoteTargetHandle.getTransformParams().setTargetSet(null);
+            remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true);
+        });
 
         // These are relatively expensive and don't need to be done this frame (RecentsView isn't
         // visible anyway), so defer by a frame to get off the critical path, e.g. app to home.
@@ -4371,12 +4373,13 @@
             TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
             tvs.setOrientationState(mOrientationState);
             tvs.setDp(mActivity.getDeviceProfile());
+            tvs.setDrawsBelowRecents(true);
             tvs.recentsViewScale.value = 1;
         }
     }
 
     /** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */
-    private void runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer) {
+    public void runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer) {
         if (mRemoteTargetHandles == null) {
             return;
         }
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 5c73fbb..2690a2a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,6 +16,7 @@
 
 package com.android.quickstep.views;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
@@ -242,7 +243,17 @@
         LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
         mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp,
                 menuOptionView, mActivity.getDeviceProfile());
-        menuOptionView.setOnClickListener(menuOption::onClick);
+        menuOptionView.setOnClickListener(view -> {
+            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+                RecentsView recentsView = mTaskView.getRecentsView();
+                recentsView.switchToScreenshot(null,
+                        () -> recentsView.finishRecentsAnimation(true /* toRecents */,
+                                false /* shouldPip */,
+                                () -> menuOption.onClick(view)));
+            } else {
+                menuOption.onClick(view);
+            }
+        });
         mOptionLayout.addView(menuOptionView);
     }
 
@@ -259,15 +270,9 @@
         BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
         int padding = getResources()
                 .getDimensionPixelSize(R.dimen.task_menu_vertical_padding);
-        if (deviceProfile.overviewShowAsGrid) {
-            // TODO(b/193432925) temporary so it doesn't look terrible on large screen
-            params.width =
-                    getContext().getResources().getDimensionPixelSize(R.dimen.task_menu_width_grid);
-        } else {
-            params.width = orientationHandler
-                    .getTaskMenuWidth(taskContainer.getThumbnailView(),
-                            deviceProfile) - (2 * padding);
-        }
+        params.width = orientationHandler
+                .getTaskMenuWidth(taskContainer.getThumbnailView(),
+                        deviceProfile) - (2 * padding);
         // Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
         params.gravity = Gravity.LEFT;
         setLayoutParams(params);
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index 9b86c73..39a6fc4 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -1,14 +1,150 @@
 package com.android.quickstep.views
 
-import android.util.Log
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.content.Context
+import android.graphics.Rect
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RectShape
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import com.android.launcher3.BaseDraggingActivity
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.R
+import com.android.launcher3.popup.ArrowPopup
+import com.android.launcher3.popup.SystemShortcut
+import com.android.launcher3.util.Themes
+import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
 
-// TODO(http://b/193432925)
-class TaskMenuViewWithArrow {
+class TaskMenuViewWithArrow<T : BaseDraggingActivity> : ArrowPopup<T> {
     companion object {
         const val TAG = "TaskMenuViewWithArrow"
 
-        fun logSomething() {
-            Log.d(TAG, "It worked!")
+        fun showForTask(taskContainer: TaskIdAttributeContainer): Boolean {
+            val activity = BaseDraggingActivity
+                    .fromContext<BaseDraggingActivity>(taskContainer.taskView.context)
+            val taskMenuViewWithArrow = activity.layoutInflater
+                    .inflate(R.layout.task_menu_with_arrow, activity.dragLayer, false) as TaskMenuViewWithArrow<*>
+
+            return taskMenuViewWithArrow.populateAndShowForTask(taskContainer)
         }
     }
+
+    constructor(context: Context) : super(context)
+    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+
+    init {
+        clipToOutline = true
+    }
+
+    private val menuWidth = context.resources.getDimensionPixelSize(R.dimen.task_menu_width_grid)
+
+    private lateinit var taskView: TaskView
+    private lateinit var optionLayout: LinearLayout
+    private lateinit var taskContainer: TaskIdAttributeContainer
+
+    override fun isOfType(type: Int): Boolean = type and TYPE_TASK_MENU != 0
+
+    override fun getTargetObjectLocation(outPos: Rect?) {
+        popupContainer.getDescendantRectRelativeToSelf(taskView.iconView, outPos)
+    }
+
+    override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
+        if (ev?.action == MotionEvent.ACTION_DOWN) {
+            if (!popupContainer.isEventOverView(this, ev)) {
+                close(true)
+                return true
+            }
+        }
+        return false
+    }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        optionLayout = findViewById(R.id.menu_option_layout)
+    }
+
+    private fun populateAndShowForTask(taskContainer: TaskIdAttributeContainer): Boolean {
+        if (isAttachedToWindow) {
+            return false
+        }
+
+        taskView = taskContainer.taskView
+        this.taskContainer = taskContainer
+        if (!populateMenu()) return false
+        show()
+        return true
+    }
+
+    /** @return true if successfully able to populate task view menu, false otherwise
+     */
+    private fun populateMenu(): Boolean {
+        // Icon may not be loaded
+        if (taskContainer.task.icon == null) return false
+
+        addMenuOptions()
+        return true
+    }
+
+    private fun addMenuOptions() {
+        // Add the options
+        TaskOverlayFactory
+            .getEnabledShortcuts(taskView, mActivityContext.deviceProfile, taskContainer)
+            .forEach { this.addMenuOption(it) }
+
+        // Add the spaces between items
+        val divider = ShapeDrawable(RectShape())
+        divider.paint.color = resources.getColor(android.R.color.transparent)
+        val dividerSpacing = resources.getDimension(R.dimen.task_menu_spacing).toInt()
+        optionLayout.showDividers = SHOW_DIVIDER_MIDDLE
+
+        // Set the orientation, which makes the menu show
+        val recentsView: RecentsView<*, *> = mActivityContext.getOverviewPanel()
+        val orientationHandler = recentsView.pagedOrientationHandler
+        val deviceProfile: DeviceProfile = mActivityContext.deviceProfile
+        orientationHandler.setTaskOptionsMenuLayoutOrientation(
+            deviceProfile,
+            optionLayout,
+            dividerSpacing,
+            divider
+        )
+    }
+
+    private fun addMenuOption(menuOption: SystemShortcut<*>) {
+        val menuOptionView = mActivityContext.layoutInflater.inflate(
+            R.layout.task_view_menu_option, this, false
+        ) as LinearLayout
+        menuOption.setIconAndLabelFor(
+            menuOptionView.findViewById(R.id.icon),
+            menuOptionView.findViewById(R.id.text)
+        )
+        val lp = menuOptionView.layoutParams as LayoutParams
+        lp.width = menuWidth
+        menuOptionView.setOnClickListener { view: View? -> menuOption.onClick(view) }
+        optionLayout.addView(menuOptionView)
+    }
+
+    override fun assignMarginsAndBackgrounds(viewGroup: ViewGroup) {
+        assignMarginsAndBackgrounds(this, Themes.getAttrColor(context, com.android.internal.R.attr.colorSurface))
+    }
+
+    override fun onCreateOpenAnimation(anim: AnimatorSet) {
+        anim.play(
+            ObjectAnimator.ofFloat(
+                taskContainer.thumbnailView, TaskThumbnailView.DIM_ALPHA,
+                TaskView.MAX_PAGE_SCRIM_ALPHA
+            )
+        )
+    }
+
+    override fun onCreateCloseAnimation(anim: AnimatorSet) {
+        anim.play(
+            ObjectAnimator.ofFloat(taskContainer.thumbnailView, TaskThumbnailView.DIM_ALPHA, 0f)
+        )
+    }
 }
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index a9db400..2d58ba5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -31,6 +31,8 @@
 import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Shader;
@@ -78,6 +80,7 @@
     private TaskOverlay mOverlay;
     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private final Paint mClearPaint = new Paint();
     private final Paint mDimmingPaintAfterClearing = new Paint();
     private final int mDimColor;
 
@@ -107,6 +110,7 @@
         super(context, attrs, defStyleAttr);
         mPaint.setFilterBitmap(true);
         mBackgroundPaint.setColor(Color.WHITE);
+        mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
         mActivity = BaseActivity.fromContext(context);
         // Initialize with placeholder value. It is overridden later by TaskView
         mFullscreenParams = TEMP_PARAMS.get(context);
@@ -271,6 +275,7 @@
             float cornerRadius) {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
+                canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
                 canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
                         mDimmingPaintAfterClearing);
                 return;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index eef5fb3..df1817e 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -94,6 +94,7 @@
 import com.android.quickstep.util.LauncherSplitScreenListener;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.TaskCornerRadius;
+import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
 import com.android.systemui.shared.recents.model.Task;
@@ -625,7 +626,21 @@
                     recentsView.getDepthController());
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
+                public void onAnimationStart(Animator animator) {
+                    recentsView.runActionOnRemoteHandles(
+                            (Consumer<RemoteTargetHandle>) remoteTargetHandle ->
+                                    remoteTargetHandle
+                                            .getTaskViewSimulator()
+                                            .setDrawsBelowRecents(false));
+                }
+
+                @Override
                 public void onAnimationEnd(Animator animator) {
+                    recentsView.runActionOnRemoteHandles(
+                            (Consumer<RemoteTargetHandle>) remoteTargetHandle ->
+                                    remoteTargetHandle
+                                            .getTaskViewSimulator()
+                                            .setDrawsBelowRecents(true));
                     mIsClickableAsLiveTile = true;
                 }
             });
@@ -809,9 +824,11 @@
     }
 
     protected boolean showTaskMenuWithContainer(IconView iconView) {
-        // TODO(http://b/193432925)
-        if (DEBUG) TaskMenuViewWithArrow.Companion.logSomething();
-        return TaskMenuView.showForTask(mTaskIdAttributeContainer[0]);
+        if (mActivity.getDeviceProfile().overviewShowAsGrid) {
+            return TaskMenuViewWithArrow.Companion.showForTask(mTaskIdAttributeContainer[0]);
+        } else {
+            return TaskMenuView.showForTask(mTaskIdAttributeContainer[0]);
+        }
     }
 
     protected void setIcon(IconView iconView, Drawable icon) {
@@ -821,15 +838,7 @@
                 if (confirmSecondSplitSelectApp()) {
                     return;
                 }
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
-                    RecentsView recentsView = getRecentsView();
-                    recentsView.switchToScreenshot(
-                            () -> recentsView.finishRecentsAnimation(true /* toRecents */,
-                                    false /* shouldPip */,
-                                    () -> showTaskMenu(iconView)));
-                } else {
-                    showTaskMenu(iconView);
-                }
+                showTaskMenu(iconView);
             });
             iconView.setOnLongClickListener(v -> {
                 requestDisallowInterceptTouchEvent(true);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 4895b10..ea65757 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -31,6 +31,7 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.tapl.AllApps;
 import com.android.launcher3.tapl.Background;
 import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel;
 import com.android.launcher3.tapl.Overview;
@@ -43,12 +44,16 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsQuickstep extends AbstractQuickStepTest {
+
+    private static final String APP_NAME = "LauncherTestApp";
+
     @Before
     public void setUp() throws Exception {
         super.setUp();
@@ -286,6 +291,31 @@
         getAndAssertBackground();
     }
 
+    // TODO(b/204830798): test with all navigation modes(add @NavigationModeSwitch annotation)
+    //  after the bug resolved.
+    @Ignore("b/205027405")
+    @Test
+    @PortraitLandscape
+    @ScreenRecord
+    public void testPressBack() throws Exception {
+        mLauncher.getWorkspace().switchToAllApps();
+        mLauncher.pressBack();
+        mLauncher.getWorkspace();
+        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+
+        AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+        allApps.freeze();
+        try {
+            allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
+        } finally {
+            allApps.unfreeze();
+        }
+        mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
+        mLauncher.pressBack();
+        mLauncher.getWorkspace();
+        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+    }
+
     @Test
     @PortraitLandscape
     public void testOverviewForTablet() throws Exception {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index fc717c9..ce06c6e 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -329,6 +329,7 @@
         }
 
         updateWorkspaceScreensPadding();
+        updateWorkspaceWidgetsSizes();
     }
 
     private void updateWorkspaceScreensPadding() {
@@ -360,6 +361,25 @@
         }
     }
 
+    private void updateWorkspaceWidgetsSizes() {
+        int numberOfScreens = mScreenOrder.size();
+        for (int i = 0; i < numberOfScreens; i++) {
+            ShortcutAndWidgetContainer shortcutAndWidgetContainer =
+                    mWorkspaceScreens.get(mScreenOrder.get(i)).getShortcutsAndWidgets();
+            int shortcutsAndWidgetCount = shortcutAndWidgetContainer.getChildCount();
+            for (int j = 0; j < shortcutsAndWidgetCount; j++) {
+                View view = shortcutAndWidgetContainer.getChildAt(j);
+                if (view instanceof LauncherAppWidgetHostView
+                        && view.getTag() instanceof LauncherAppWidgetInfo) {
+                    LauncherAppWidgetInfo launcherAppWidgetInfo =
+                            (LauncherAppWidgetInfo) view.getTag();
+                    WidgetSizes.updateWidgetSizeRanges((LauncherAppWidgetHostView) view,
+                            mLauncher, launcherAppWidgetInfo.spanX, launcherAppWidgetInfo.spanY);
+                }
+            }
+        }
+    }
+
     /**
      * Estimates the size of an item using spans: hSpan, vSpan.
      *
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 97052b2..dd58123 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -41,11 +41,10 @@
     }
 
     /**
-     *
      * @param target The view the accessibility event is initialized on.
      *               If null, this method has no effect.
-     * @param type See TYPE_ constants defined in {@link AccessibilityEvent}.
-     * @param text Optional text to add to the event, which will be announced to the user.
+     * @param type   See TYPE_ constants defined in {@link AccessibilityEvent}.
+     * @param text   Optional text to add to the event, which will be announced to the user.
      */
     public static void sendCustomAccessibilityEvent(@Nullable View target, int type,
             @Nullable String text) {
@@ -97,6 +96,16 @@
                 null);
     }
 
+    /**
+     * Notify running tests of a folder opened.
+     */
+    public static void sendFolderOpenedEventToTest(Context context) {
+        final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
+        if (accessibilityManager == null) return;
+
+        sendEventToTest(accessibilityManager, context, TestProtocol.FOLDER_OPENED_MESSAGE, null);
+    }
+
     private static void sendEventToTest(
             AccessibilityManager accessibilityManager,
             Context context, String eventTag, Bundle data) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 879739f..daef682 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -78,6 +78,7 @@
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
 import com.android.launcher3.accessibility.FolderAccessibilityHelper;
 import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragController.DragListener;
@@ -687,6 +688,7 @@
             public void onAnimationEnd(Animator animation) {
                 mState = STATE_OPEN;
                 announceAccessibilityChanges();
+                AccessibilityManagerCompat.sendFolderOpenedEventToTest(getContext());
 
                 mContent.setFocusOnFirstChild();
             }
@@ -1265,7 +1267,8 @@
 
         PendingAddShortcutInfo pasi = d.dragInfo instanceof PendingAddShortcutInfo
                 ? (PendingAddShortcutInfo) d.dragInfo : null;
-        WorkspaceItemInfo pasiSi = pasi != null ? pasi.activityInfo.createWorkspaceItemInfo() : null;
+        WorkspaceItemInfo pasiSi =
+                pasi != null ? pasi.activityInfo.createWorkspaceItemInfo() : null;
         if (pasi != null && pasiSi == null) {
             // There is no WorkspaceItemInfo, so we have to go through a configuration activity.
             pasi.container = mInfo.id;
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 2230914..5a1e4bf 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -234,7 +234,7 @@
      * @param backgroundColor When Color.TRANSPARENT, we get color from {@link #mColorIds}.
      *                        Otherwise, we will use this color for all child views.
      */
-    private void assignMarginsAndBackgrounds(ViewGroup viewGroup, int backgroundColor) {
+    protected void assignMarginsAndBackgrounds(ViewGroup viewGroup, int backgroundColor) {
         int[] colors = null;
         if (backgroundColor == Color.TRANSPARENT) {
             // Lazily get the colors so they match the current wallpaper colors.
@@ -445,7 +445,7 @@
         animateOpen();
     }
 
-    private void setupForDisplay() {
+    protected void setupForDisplay() {
         setVisibility(View.INVISIBLE);
         mIsOpen = true;
         getPopupContainer().addView(this);
@@ -482,7 +482,7 @@
         mArrow.setVisibility(show && shouldAddArrow() ? VISIBLE : INVISIBLE);
     }
 
-    private void addArrow() {
+    protected void addArrow() {
         getPopupContainer().addView(mArrow);
         mArrow.setX(getX() + getArrowLeft());
 
@@ -686,12 +686,13 @@
         return getChildCount() > 0 ? getChildAt(0) : this;
     }
 
-    private void animateOpen() {
+    protected void animateOpen() {
         setVisibility(View.VISIBLE);
 
         mOpenCloseAnimator = getOpenCloseAnimator(true, OPEN_DURATION, OPEN_FADE_START_DELAY,
                 OPEN_FADE_DURATION, OPEN_CHILD_FADE_START_DELAY, OPEN_CHILD_FADE_DURATION,
                 DECELERATED_EASE);
+        onCreateOpenAnimation(mOpenCloseAnimator);
         mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -785,6 +786,11 @@
     }
 
     /**
+     * Called when creating the open transition allowing subclass can add additional animations.
+     */
+    protected void onCreateOpenAnimation(AnimatorSet anim) { }
+
+    /**
      * Called when creating the close transition allowing subclass can add additional animations.
      */
     protected void onCreateCloseAnimation(AnimatorSet anim) { }
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 5a9c074..17d925c 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -21,6 +21,7 @@
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Insets;
 import android.os.Build;
 import android.os.Bundle;
@@ -148,6 +149,14 @@
                         TestProtocol.TEST_INFO_RESPONSE_FIELD, TestLogging.sHadEventsNotFromTest);
                 return response;
 
+            case TestProtocol.REQUEST_START_DRAG_THRESHOLD: {
+                final Resources resources = mContext.getResources();
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        resources.getDimensionPixelSize(R.dimen.deep_shortcuts_start_drag_threshold)
+                                + resources.getDimensionPixelSize(R.dimen.pre_drag_view_scale));
+                return response;
+            }
+
             default:
                 return null;
         }
@@ -193,6 +202,7 @@
 
     /**
      * Generic interface for setting a fiend in bundle
+     *
      * @param <T> the type of value being set
      */
     public interface BundleSetter<T> {
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 5bf0342..91c7221 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -25,6 +25,7 @@
     public static final String SCROLL_FINISHED_MESSAGE = "TAPL_SCROLL_FINISHED";
     public static final String PAUSE_DETECTED_MESSAGE = "TAPL_PAUSE_DETECTED";
     public static final String DISMISS_ANIMATION_ENDS_MESSAGE = "TAPL_DISMISS_ANIMATION_ENDS";
+    public static final String FOLDER_OPENED_MESSAGE = "TAPL_FOLDER_OPENED";
     public static final int NORMAL_STATE_ORDINAL = 0;
     public static final int SPRING_LOADED_STATE_ORDINAL = 1;
     public static final int OVERVIEW_STATE_ORDINAL = 2;
@@ -99,6 +100,7 @@
     public static final String REQUEST_CLEAR_DATA = "clear-data";
     public static final String REQUEST_IS_TABLET = "is-tablet";
     public static final String REQUEST_IS_TWO_PANELS = "is-two-panel";
+    public static final String REQUEST_START_DRAG_THRESHOLD = "start-drag-threshold";
     public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT =
             "get-activities-created-count";
     public static final String REQUEST_GET_ACTIVITIES = "get-activities";
@@ -119,7 +121,9 @@
     public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
 
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
+    public static final String TASKBAR_WINDOW_CRASH = "b/201305599";
     public static final String TASK_VIEW_ID_CRASH = "b/195430732";
     public static final String NO_DROP_TARGET = "b/195031154";
     public static final String NULL_INT_SET = "b/200572078";
+    public static final String NO_SCREENSHOT = "b/202414125";
 }
diff --git a/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java b/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java
index 9c32e42..57f8bc7 100644
--- a/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.widget;
 
+import android.annotation.SuppressLint;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -30,6 +31,9 @@
 
 import com.android.launcher3.R;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 /**
  * A widget host views created while the host has not bind to the system service.
  */
@@ -75,8 +79,22 @@
                 && mSetupTextLayout.getWidth() == availableWidth) {
             return;
         }
-        mSetupTextLayout = new StaticLayout(info.label, mPaint, availableWidth,
-                Layout.Alignment.ALIGN_CENTER, 1, 0, true);
+        try {
+            mSetupTextLayout = new StaticLayout(info.label, mPaint, availableWidth,
+                    Layout.Alignment.ALIGN_CENTER, 1, 0, true);
+        } catch (IllegalArgumentException e) {
+            @SuppressLint("DrawAllocation") StringWriter stringWriter = new StringWriter();
+            @SuppressLint("DrawAllocation") PrintWriter printWriter = new PrintWriter(stringWriter);
+            mActivity.getDeviceProfile().dump(/*prefix=*/"", printWriter);
+            printWriter.flush();
+            String message = "b/203530620 "
+                    + "- availableWidth: " + availableWidth
+                    + ", getMeasuredWidth: " + getMeasuredWidth()
+                    + ", getPaddingLeft: " + getPaddingLeft()
+                    + ", getPaddingRight: " + getPaddingRight()
+                    + ", deviceProfile: " + stringWriter.toString();
+            throw new IllegalArgumentException(message, e);
+        }
     }
 
     @Override
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 0ffbeeb..b3457cd 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -211,7 +211,8 @@
     }
 
     protected TestRule getRulesInsideActivityMonitor() {
-        final RuleChain inner = RuleChain.outerRule(new PortraitLandscapeRunner(this))
+        final RuleChain inner = RuleChain
+                .outerRule(new PortraitLandscapeRunner(this))
                 .around(new FailureWatcher(mDevice, mLauncher));
 
         return TestHelpers.isInLauncherProcess()
@@ -345,13 +346,19 @@
     }
 
     // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call
-    // expecting
-    // the results of that gesture because the wait can hide flakeness.
+    // expecting the results of that gesture because the wait can hide flakeness.
     protected void waitForState(String message, Supplier<LauncherState> state) {
         waitForLauncherCondition(message,
                 launcher -> launcher.getStateManager().getCurrentStableState() == state.get());
     }
 
+    // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call
+    // expecting the results of that gesture because the wait can hide flakeness.
+    protected void waitForStableState(String message, Supplier<LauncherState> state) {
+        waitForLauncherCondition(message,
+                launcher -> launcher.getStateManager().isInStableState(state.get()));
+    }
+
     protected void waitForResumed(String message) {
         waitForLauncherCondition(message, launcher -> launcher.hasBeenResumed());
     }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 881f50c..f29ac23 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -34,6 +34,8 @@
 import com.android.launcher3.tapl.AppIcon;
 import com.android.launcher3.tapl.AppIconMenu;
 import com.android.launcher3.tapl.AppIconMenuItem;
+import com.android.launcher3.tapl.Folder;
+import com.android.launcher3.tapl.FolderIcon;
 import com.android.launcher3.tapl.Widgets;
 import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.views.OptionsPopupView;
@@ -369,6 +371,71 @@
         }
     }
 
+    private AppIcon createShortcutIfNotExist(String name) {
+        AppIcon appIcon = mLauncher.getWorkspace().tryGetWorkspaceAppIcon(name);
+        if (appIcon == null) {
+            AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+            allApps.freeze();
+            try {
+                appIcon = allApps.getAppIcon(name);
+                appIcon.dragToWorkspace(false, false);
+            } finally {
+                allApps.unfreeze();
+            }
+            appIcon = mLauncher.getWorkspace().getWorkspaceAppIcon(name);
+        }
+        return appIcon;
+    }
+
+    @Ignore("b/205014516")
+    @Test
+    @PortraitLandscape
+    public void testDragToFolder() throws Exception {
+        final AppIcon playStoreIcon = createShortcutIfNotExist("Play Store");
+        final AppIcon gmailIcon = createShortcutIfNotExist("Gmail");
+
+        FolderIcon folderIcon = gmailIcon.dragToIcon(playStoreIcon);
+
+        Folder folder = folderIcon.open();
+        folder.getAppIcon("Play Store");
+        folder.getAppIcon("Gmail");
+        Workspace workspace = folder.close();
+
+        assertNull("Gmail should be moved to a folder.",
+                workspace.tryGetWorkspaceAppIcon("Gmail"));
+        assertNull("Play Store should be moved to a folder.",
+                workspace.tryGetWorkspaceAppIcon("Play Store"));
+
+        final AppIcon youTubeIcon = createShortcutIfNotExist("YouTube");
+
+        folderIcon = youTubeIcon.dragToIcon(folderIcon);
+        folder = folderIcon.open();
+        folder.getAppIcon("YouTube");
+        folder.close();
+    }
+
+    @Ignore("b/205027405")
+    @Test
+    @PortraitLandscape
+    public void testPressBack() throws Exception {
+        mLauncher.getWorkspace().switchToAllApps();
+        mLauncher.pressBack();
+        mLauncher.getWorkspace();
+        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+
+        AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
+        allApps.freeze();
+        try {
+            allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
+        } finally {
+            allApps.unfreeze();
+        }
+        mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
+        mLauncher.pressBack();
+        mLauncher.getWorkspace();
+        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+    }
+
     public static String getAppPackageName() {
         return getInstrumentation().getContext().getPackageName();
     }
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index 27a2375..2087bfe 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -91,9 +91,9 @@
     public void workTabExists() {
         mDevice.pressHome();
         waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
-        waitForState("Launcher internal state didn't switch to Normal", () -> NORMAL);
+        waitForStableState("Launcher internal state didn't switch to Normal", () -> NORMAL);
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
-        waitForState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS);
+        waitForStableState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS);
         waitForLauncherCondition("Personal tab is missing",
                 launcher -> launcher.getAppsView().isPersonalTabVisible(),
                 LauncherInstrumentation.WAIT_TIME_MS);
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 21099b4..6da59da 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -16,8 +16,11 @@
 
 package com.android.launcher3.tapl;
 
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiObject2;
@@ -29,7 +32,7 @@
 /**
  * App icon, whether in all apps or in workspace/
  */
-public final class AppIcon extends Launchable {
+public final class AppIcon extends Launchable implements FolderDragTarget {
 
     private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onAllAppsItemLongClick");
 
@@ -61,6 +64,29 @@
         }
     }
 
+    /**
+     * Drag the AppIcon to the given position of other icon. The drag must result in a folder.
+     *
+     * @param target the destination icon.
+     */
+    @NonNull
+    public FolderIcon dragToIcon(FolderDragTarget target) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer("want to drag icon")) {
+            final Rect dropBounds = target.getDropLocationBounds();
+            Workspace.dragIconToWorkspace(
+                    mLauncher, this,
+                    () -> {
+                        final Rect bounds = target.getDropLocationBounds();
+                        return new Point(bounds.centerX(), bounds.centerY());
+                    },
+                    getLongPressIndicator());
+            FolderIcon result = target.getTargetFolder(dropBounds);
+            mLauncher.assertTrue("Can't find the target folder.", result != null);
+            return result;
+        }
+    }
+
     @Override
     protected void addExpectedEventsForLongClick() {
         mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT);
@@ -80,4 +106,20 @@
     protected String launchableType() {
         return "app icon";
     }
+
+    @Override
+    public Rect getDropLocationBounds() {
+        return mLauncher.getVisibleBounds(mObject);
+    }
+
+    @Override
+    public FolderIcon getTargetFolder(Rect bounds) {
+        for (FolderIcon folderIcon : mLauncher.getWorkspace().getFolderIcons()) {
+            final Rect folderIconBounds = folderIcon.getDropLocationBounds();
+            if (bounds.contains(folderIconBounds.centerX(), folderIconBounds.centerY())) {
+                return folderIcon;
+            }
+        }
+        return null;
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Folder.java b/tests/tapl/com/android/launcher3/tapl/Folder.java
new file mode 100644
index 0000000..dba308d
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Folder.java
@@ -0,0 +1,76 @@
+/*
+ * 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.tapl;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiObject2;
+
+public class Folder {
+
+    protected static final String FOLDER_CONTENT_RES_ID = "folder_content";
+
+    private final UiObject2 mContainer;
+    private final LauncherInstrumentation mLauncher;
+
+    Folder(LauncherInstrumentation launcher) {
+        this.mLauncher = launcher;
+        this.mContainer = launcher.waitForLauncherObject(FOLDER_CONTENT_RES_ID);
+    }
+
+    /**
+     * Find an app icon with given name or raise assertion error.
+     */
+    @NonNull
+    public AppIcon getAppIcon(String appName) {
+        try (LauncherInstrumentation.Closable ignored = mLauncher.addContextLayer(
+                "Want to get app icon in folder")) {
+            return new AppIcon(mLauncher,
+                    mLauncher.waitForObjectInContainer(
+                            mContainer,
+                            AppIcon.getAppIconSelector(appName, mLauncher)));
+        }
+    }
+
+    private void touchOutsideFolder() {
+        Rect containerBounds = mLauncher.getVisibleBounds(this.mContainer);
+        final long downTime = SystemClock.uptimeMillis();
+        Point containerLeftTopCorner = new Point(containerBounds.left - 1, containerBounds.top - 1);
+        mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
+                containerLeftTopCorner, LauncherInstrumentation.GestureScope.INSIDE);
+        mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP,
+                containerLeftTopCorner, LauncherInstrumentation.GestureScope.INSIDE);
+    }
+
+    /**
+     * CLose opened folder if possible. It throws assertion error if the folder is already closed.
+     */
+    public Workspace close() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "Want to close opened folder")) {
+            mLauncher.waitForLauncherObject(FOLDER_CONTENT_RES_ID);
+            touchOutsideFolder();
+            mLauncher.waitUntilLauncherObjectGone(FOLDER_CONTENT_RES_ID);
+            return mLauncher.getWorkspace();
+        }
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/FolderDragTarget.java b/tests/tapl/com/android/launcher3/tapl/FolderDragTarget.java
new file mode 100644
index 0000000..d797418
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/FolderDragTarget.java
@@ -0,0 +1,25 @@
+/*
+ * 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.tapl;
+
+import android.graphics.Rect;
+
+public interface FolderDragTarget {
+    Rect getDropLocationBounds();
+
+    FolderIcon getTargetFolder(Rect bounds);
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/FolderIcon.java b/tests/tapl/com/android/launcher3/tapl/FolderIcon.java
new file mode 100644
index 0000000..2e79d70
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/FolderIcon.java
@@ -0,0 +1,64 @@
+/*
+ * 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.tapl;
+
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiObject2;
+
+import com.android.launcher3.testing.TestProtocol;
+
+/**
+ * Folder Icon, an app folder in workspace.
+ */
+public class FolderIcon implements FolderDragTarget {
+
+    protected final UiObject2 mObject;
+    protected final LauncherInstrumentation mLauncher;
+
+    FolderIcon(LauncherInstrumentation launcher, UiObject2 icon) {
+        mObject = icon;
+        mLauncher = launcher;
+    }
+
+    /**
+     * Open and return a folder or raise assertion error.
+     */
+    @NonNull
+    public Folder open() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer("open folder")) {
+            mLauncher.executeAndWaitForLauncherEvent(() -> mLauncher.clickLauncherObject(mObject),
+                    event -> TestProtocol.FOLDER_OPENED_MESSAGE.equals(
+                            event.getClassName().toString()),
+                    () -> "Fail to open folder.",
+                    "open folder");
+        }
+        return new Folder(mLauncher);
+    }
+
+    @Override
+    public Rect getDropLocationBounds() {
+        return mLauncher.getVisibleBounds(mObject.getParent());
+    }
+
+    @Override
+    public FolderIcon getTargetFolder(Rect bounds) {
+        return this;
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 7ffdf4c..e2d0238 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -21,6 +21,7 @@
 import static android.content.pm.PackageManager.MATCH_ALL;
 import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
 
+import static com.android.launcher3.tapl.Folder.FOLDER_CONTENT_RES_ID;
 import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName;
 import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
 
@@ -75,11 +76,13 @@
 import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Deque;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
@@ -108,6 +111,9 @@
     static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
     static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
 
+    static final Pattern EVENT_KEY_BACK_DOWN = getKeyEventPattern("ACTION_DOWN", "KEYCODE_BACK");
+    static final Pattern EVENT_KEY_BACK_UP = getKeyEventPattern("ACTION_UP", "KEYCODE_BACK");
+
     private final String mLauncherPackage;
     private Boolean mIsLauncher3;
     private long mTestStartTime = -1;
@@ -123,7 +129,8 @@
     // Where the gesture happens: outside of Launcher, inside or from inside to outside and
     // whether the gesture recognition triggers pilfer.
     public enum GestureScope {
-        OUTSIDE_WITHOUT_PILFER, OUTSIDE_WITH_PILFER, INSIDE, INSIDE_TO_OUTSIDE
+        OUTSIDE_WITHOUT_PILFER, OUTSIDE_WITH_PILFER, INSIDE, INSIDE_TO_OUTSIDE,
+        INSIDE_TO_OUTSIDE_WITHOUT_PILFER,
     }
 
     // Base class for launcher containers.
@@ -193,6 +200,10 @@
         return getTouchEventPattern("TouchInteractionService.onInputEvent", action);
     }
 
+    private static Pattern getKeyEventPattern(String action, String keyCode) {
+        return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode);
+    }
+
     /**
      * Constructs the root of TAPL hierarchy. You get all other objects from it.
      */
@@ -768,6 +779,47 @@
     }
 
     /**
+     * Get the resource ID of visible floating view.
+     */
+    private Optional<String> getFloatingResId() {
+        if (hasLauncherObject(CONTEXT_MENU_RES_ID)) {
+            return Optional.of(CONTEXT_MENU_RES_ID);
+        }
+        if (hasLauncherObject(FOLDER_CONTENT_RES_ID)) {
+            return Optional.of(FOLDER_CONTENT_RES_ID);
+        }
+        return Optional.empty();
+    }
+
+    /**
+     * Using swiping up gesture to dismiss closable floating views, such as Menu or Folder Content.
+     */
+    private void swipeUpToCloseFloatingView(boolean gestureStartFromLauncher) {
+        final Point displaySize = getRealDisplaySize();
+
+        final Optional<String> floatingRes = getFloatingResId();
+
+        if (!floatingRes.isPresent()) {
+            return;
+        }
+
+        GestureScope gestureScope = gestureStartFromLauncher
+                ? (isTablet() ? GestureScope.INSIDE : GestureScope.INSIDE_TO_OUTSIDE)
+                : GestureScope.OUTSIDE_WITH_PILFER;
+        linearGesture(
+                displaySize.x / 2, displaySize.y - 1,
+                displaySize.x / 2, 0,
+                ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
+                false, gestureScope);
+
+        try (LauncherInstrumentation.Closable c1 = addContextLayer(
+                String.format("Swiped up from floating view %s to home", floatingRes.get()))) {
+            waitUntilLauncherObjectGone(floatingRes.get());
+            waitForLauncherObject(getAnyObjectSelector());
+        }
+    }
+
+    /**
      * Presses nav bar home button.
      *
      * @return the Workspace object.
@@ -791,21 +843,9 @@
                         ? !isLauncher3() || hasLauncherObject(WORKSPACE_RES_ID)
                         : isLauncherVisible();
 
-                if (hasLauncherObject(CONTEXT_MENU_RES_ID)) {
-                    GestureScope gestureScope = gestureStartFromLauncher
-                            ? (isTablet() ? GestureScope.INSIDE : GestureScope.INSIDE_TO_OUTSIDE)
-                            : GestureScope.OUTSIDE_WITH_PILFER;
-                    linearGesture(
-                            displaySize.x / 2, displaySize.y - 1,
-                            displaySize.x / 2, 0,
-                            ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
-                            false, gestureScope);
-                    try (LauncherInstrumentation.Closable c1 = addContextLayer(
-                            "Swiped up from context menu to home")) {
-                        waitUntilLauncherObjectGone(CONTEXT_MENU_RES_ID);
-                        waitForLauncherObject(getAnyObjectSelector());
-                    }
-                }
+                // CLose floating views before going back to home.
+                swipeUpToCloseFloatingView(gestureStartFromLauncher);
+
                 if (hasLauncherObject(WORKSPACE_RES_ID)) {
                     log(action = "already at home");
                 } else {
@@ -848,6 +888,38 @@
         }
     }
 
+    /**
+     * Press navbar back button or swipe back if in gesture navigation mode.
+     */
+    public void pressBack() {
+        try (Closable e = eventsCheck(); Closable c = addContextLayer("want to press back")) {
+            waitForLauncherInitialized();
+            final boolean launcherVisible =
+                    isTablet() ? isLauncherContainerVisible() : isLauncherVisible();
+            if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
+                final Point displaySize = getRealDisplaySize();
+                final GestureScope gestureScope =
+                        launcherVisible ? GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER
+                                : GestureScope.OUTSIDE_WITHOUT_PILFER;
+                linearGesture(0, displaySize.y / 2, displaySize.x / 2, displaySize.y / 2,
+                        10, false, gestureScope);
+            } else {
+                waitForNavigationUiObject("back").click();
+                if (isTablet()) {
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_UP);
+                } else if (!isLauncher3() && getNavigationModel() == NavigationModel.TWO_BUTTON) {
+                    expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
+                    expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
+                }
+            }
+            if (launcherVisible) {
+                expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_DOWN);
+                expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KEY_BACK_UP);
+            }
+        }
+    }
+
     private static BySelector getAnyObjectSelector() {
         return By.textStartsWith("");
     }
@@ -857,6 +929,11 @@
         return hasLauncherObject(getAnyObjectSelector());
     }
 
+    boolean isLauncherContainerVisible() {
+        final String[] containerResources = {WORKSPACE_RES_ID, OVERVIEW_RES_ID, APPS_RES_ID};
+        return Arrays.stream(containerResources).anyMatch(r -> hasLauncherObject(r));
+    }
+
     /**
      * Gets the Workspace object if the current state is "active home", i.e. workspace. Fails if the
      * launcher is not in that state.
@@ -1383,6 +1460,7 @@
                 break;
             case MotionEvent.ACTION_UP:
                 if (notLauncher3 && gestureScope != GestureScope.INSIDE
+                        && gestureScope != GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER
                         && (gestureScope == GestureScope.OUTSIDE_WITH_PILFER
                         || gestureScope == GestureScope.INSIDE_TO_OUTSIDE)) {
                     expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS);
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 288c853..0145690 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -36,7 +36,10 @@
 
 import com.android.launcher3.testing.TestProtocol;
 
+import java.util.List;
+import java.util.function.Supplier;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 /**
  * Operations on the workspace screen.
@@ -170,40 +173,100 @@
                 mHotseat, AppIcon.getAppIconSelector(appName, mLauncher)));
     }
 
-    static void dragIconToWorkspace(
-            LauncherInstrumentation launcher, Launchable launchable, Point dest,
-            String longPressIndicator, boolean startsActivity, boolean isWidgetShortcut,
-            Runnable expectLongClickEvents) {
-        LauncherInstrumentation.log("dragIconToWorkspace: begin");
-        final Point launchableCenter = launchable.getObject().getVisibleCenter();
-        final long downTime = SystemClock.uptimeMillis();
-        launcher.runToState(
-                () -> {
-                    launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
-                            launchableCenter, LauncherInstrumentation.GestureScope.INSIDE);
-                    LauncherInstrumentation.log("dragIconToWorkspace: sent down");
-                    expectLongClickEvents.run();
-                    launcher.waitForLauncherObject(longPressIndicator);
-                    LauncherInstrumentation.log("dragIconToWorkspace: indicator");
-                    launcher.movePointer(launchableCenter, dest, 10, downTime, true,
-                            LauncherInstrumentation.GestureScope.INSIDE);
-                },
-                SPRING_LOADED_STATE_ORDINAL,
-                "long-pressing and moving");
-        LauncherInstrumentation.log("dragIconToWorkspace: moved pointer");
+    private static int getStartDragThreshold(LauncherInstrumentation launcher) {
+        return launcher.getTestInfo(TestProtocol.REQUEST_START_DRAG_THRESHOLD).getInt(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
+    /**
+     * Finds folder icons in the current workspace.
+     *
+     * @return a list of folder icons.
+     */
+    List<FolderIcon> getFolderIcons() {
+        final UiObject2 workspace = verifyActiveContainer();
+        return mLauncher.getObjectsInContainer(workspace, "folder_icon_name").stream().map(
+                o -> new FolderIcon(mLauncher, o)).collect(Collectors.toList());
+    }
+
+    /**
+     * Drag an icon up with a short distance that makes workspace go to spring loaded state.
+     *
+     * @return the position after dragging.
+     */
+    private static Point dragIconToSpringLoaded(LauncherInstrumentation launcher, long downTime,
+            UiObject2 icon,
+            String longPressIndicator, Runnable expectLongClickEvents) {
+        final Point iconCenter = icon.getVisibleCenter();
+        final Point dragStartCenter = new Point(iconCenter.x,
+                iconCenter.y - getStartDragThreshold(launcher));
+
+        launcher.runToState(() -> {
+            launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
+                    iconCenter, LauncherInstrumentation.GestureScope.INSIDE);
+            LauncherInstrumentation.log("dragIconToSpringLoaded: sent down");
+            expectLongClickEvents.run();
+            launcher.waitForLauncherObject(longPressIndicator);
+            LauncherInstrumentation.log("dragIconToSpringLoaded: indicator");
+            launcher.movePointer(iconCenter, dragStartCenter, 10, downTime, true,
+                    LauncherInstrumentation.GestureScope.INSIDE);
+        }, SPRING_LOADED_STATE_ORDINAL, "long-pressing and triggering drag start");
+        return dragStartCenter;
+    }
+
+    private static void dropDraggedIcon(LauncherInstrumentation launcher, Point dest, long downTime,
+            @Nullable Runnable expectedEvents) {
         launcher.runToState(
                 () -> launcher.sendPointer(
                         downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest,
                         LauncherInstrumentation.GestureScope.INSIDE),
                 NORMAL_STATE_ORDINAL,
                 "sending UP event");
-        if (startsActivity || isWidgetShortcut) {
-            launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_START);
+        if (expectedEvents != null) {
+            expectedEvents.run();
         }
-        LauncherInstrumentation.log("dragIconToWorkspace: end");
+        LauncherInstrumentation.log("dropIcon: end");
         launcher.waitUntilLauncherObjectGone("drop_target_bar");
     }
 
+    static void dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable,
+            Point dest, String longPressIndicator, boolean startsActivity, boolean isWidgetShortcut,
+            Runnable expectLongClickEvents) {
+        Runnable expectDropEvents = null;
+        if (startsActivity || isWidgetShortcut) {
+            expectDropEvents = () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+                    LauncherInstrumentation.EVENT_START);
+        }
+        dragIconToWorkspace(launcher, launchable, () -> dest, longPressIndicator,
+                expectLongClickEvents, expectDropEvents);
+    }
+
+    /**
+     * Drag icon in workspace to else where.
+     * This function expects the launchable is inside the workspace and there is no drop event.
+     */
+    static void dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable,
+            Supplier<Point> destSupplier, String longPressIndicator) {
+        dragIconToWorkspace(launcher, launchable, destSupplier, longPressIndicator,
+                () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT), null);
+    }
+
+    static void dragIconToWorkspace(
+            LauncherInstrumentation launcher, Launchable launchable, Supplier<Point> dest,
+            String longPressIndicator, Runnable expectLongClickEvents,
+            @Nullable Runnable expectDropEvents) {
+        try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer(
+                "want to drag icon to workspace")) {
+            final long downTime = SystemClock.uptimeMillis();
+            final Point dragStartCenter = dragIconToSpringLoaded(launcher, downTime,
+                    launchable.getObject(), longPressIndicator, expectLongClickEvents);
+            final Point targetDest = dest.get();
+            launcher.movePointer(dragStartCenter, targetDest, 10, downTime, true,
+                    LauncherInstrumentation.GestureScope.INSIDE);
+            dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
+        }
+    }
+
     /**
      * Flings to get to screens on the right. Waits for scrolling and a possible overscroll
      * recoil to complete.