Merge "TAPL: increasing FLINGS_FOR_DISMISS_LIMIT" into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index 2feb141..25593e6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -24,7 +24,10 @@
 LOCAL_AAPT2_ONLY := true
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := libPluginCore
+ifneq (,$(wildcard frameworks/base))
+else
+    LOCAL_STATIC_JAVA_LIBRARIES:= libPluginCore
+endif
 
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src_plugins)
@@ -197,6 +200,7 @@
 LOCAL_PRIVILEGED_MODULE := true
 LOCAL_PRODUCT_MODULE := true
 LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
+LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
 
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/quickstep/res \
diff --git a/go/quickstep/res/drawable/clear_all_button.xml b/go/quickstep/res/drawable/clear_all_button.xml
new file mode 100644
index 0000000..acac32d
--- /dev/null
+++ b/go/quickstep/res/drawable/clear_all_button.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/clear_all_button_bg"/>
+    <corners android:radius="4dp"/>
+</shape>
diff --git a/go/quickstep/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml
index ff3dcef9..fddb1d3 100644
--- a/go/quickstep/res/layout/icon_recents_root_view.xml
+++ b/go/quickstep/res/layout/icon_recents_root_view.xml
@@ -19,13 +19,31 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
-    <androidx.recyclerview.widget.RecyclerView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/recent_task_recycler_view"
+    <LinearLayout
+        android:id="@+id/recent_task_content_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:scrollbars="none"
-        android:visibility="gone"/>
+        android:orientation="vertical"
+        android:visibility="gone">
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/recent_task_recycler_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:scrollbars="none"/>
+        <Button
+            android:id="@+id/clear_all_button"
+            android:layout_width="@dimen/clear_all_button_width"
+            android:layout_height="@dimen/clear_all_button_height"
+            android:layout_gravity="center_horizontal"
+            android:layout_marginVertical="@dimen/task_item_half_vert_margin"
+            android:background="@drawable/clear_all_button"
+            android:gravity="center"
+            android:text="@string/recents_clear_all"
+            android:textAllCaps="false"
+            android:textColor="@color/clear_all_button_text"
+            android:textSize="14sp"/>
+    </LinearLayout>
     <TextView
         android:id="@+id/recent_task_empty_view"
         android:layout_width="match_parent"
diff --git a/go/quickstep/res/values/colors.xml b/go/quickstep/res/values/colors.xml
new file mode 100644
index 0000000..ff9dc9c
--- /dev/null
+++ b/go/quickstep/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <color name="clear_all_button_bg">#FFDADCE0</color>
+    <color name="clear_all_button_text">#FF5F6368</color>
+</resources>
diff --git a/go/quickstep/res/values/dimens.xml b/go/quickstep/res/values/dimens.xml
index 7269704..e2fa387 100644
--- a/go/quickstep/res/values/dimens.xml
+++ b/go/quickstep/res/values/dimens.xml
@@ -20,4 +20,7 @@
     <dimen name="task_thumbnail_height">60dp</dimen>
     <dimen name="task_thumbnail_width">36dp</dimen>
     <dimen name="task_icon_size">36dp</dimen>
+
+    <dimen name="clear_all_button_width">106dp</dimen>
+    <dimen name="clear_all_button_height">36dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/go/quickstep/src/com/android/quickstep/TaskInputController.java b/go/quickstep/src/com/android/quickstep/TaskActionController.java
similarity index 64%
rename from go/quickstep/src/com/android/quickstep/TaskInputController.java
rename to go/quickstep/src/com/android/quickstep/TaskActionController.java
index 8433007..b2d495b 100644
--- a/go/quickstep/src/com/android/quickstep/TaskInputController.java
+++ b/go/quickstep/src/com/android/quickstep/TaskActionController.java
@@ -23,24 +23,25 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 /**
- * Controller responsible for task logic that occurs on various input to the recents view.
+ * Controller that provides logic for task-related commands on recents and updating the model/view
+ * as appropriate.
  */
-public final class TaskInputController {
+public final class TaskActionController {
 
     private final TaskListLoader mLoader;
     private final TaskAdapter mAdapter;
 
-    public TaskInputController(TaskListLoader loader,TaskAdapter adapter) {
+    public TaskActionController(TaskListLoader loader, TaskAdapter adapter) {
         mLoader = loader;
         mAdapter = adapter;
     }
 
     /**
-     * Logic that occurs when a task view is tapped. Launches the respective task.
+     * Launch the task associated with the task holder, animating into the app.
      *
-     * @param viewHolder the task view holder that has been tapped
+     * @param viewHolder the task view holder to launch
      */
-    public void onTaskClicked(TaskHolder viewHolder) {
+    public void launchTask(TaskHolder viewHolder) {
         TaskItemView itemView = (TaskItemView) (viewHolder.itemView);
         View v = itemView.getThumbnailView();
         int left = 0;
@@ -53,7 +54,12 @@
                 opts, null /* resultCallback */, null /* resultCallbackHandler */);
     }
 
-    public void onTaskSwiped(TaskHolder viewHolder) {
+    /**
+     * Removes the task holder and the task, updating the model and the view.
+     *
+     * @param viewHolder the task view holder to remove
+     */
+    public void removeTask(TaskHolder viewHolder) {
         int position = viewHolder.getAdapterPosition();
         Task task = viewHolder.getTask();
         ActivityManagerWrapper.getInstance().removeTask(task.key.id);
@@ -61,6 +67,14 @@
         mAdapter.notifyItemRemoved(position);
     }
 
-    // TODO: Implement "Clear all" and notify adapter that data has updated
-
+    /**
+     * Clears all tasks and updates the model and view.
+     */
+    public void clearAllTasks() {
+        // TODO: Play an animation so transition is more natural.
+        int count = mAdapter.getItemCount();
+        ActivityManagerWrapper.getInstance().removeAllRecentTasks();
+        mLoader.clearAllTasks();
+        mAdapter.notifyItemRangeRemoved(0 /* positionStart */, count);
+    }
 }
diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
index 9a2c0f8..e56cc51 100644
--- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java
+++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
@@ -39,14 +39,14 @@
     private static final String TAG = "TaskAdapter";
     private final TaskListLoader mLoader;
     private final ArrayMap<Integer, TaskItemView> mTaskIdToViewMap = new ArrayMap<>();
-    private TaskInputController mInputController;
+    private TaskActionController mTaskActionController;
 
     public TaskAdapter(@NonNull TaskListLoader loader) {
         mLoader = loader;
     }
 
-    public void setInputController(TaskInputController inputController) {
-        mInputController = inputController;
+    public void setActionController(TaskActionController taskActionController) {
+        mTaskActionController = taskActionController;
     }
 
     /**
@@ -64,7 +64,7 @@
         TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext())
                 .inflate(R.layout.task_item_view, parent, false);
         TaskHolder holder = new TaskHolder(itemView);
-        itemView.setOnClickListener(view -> mInputController.onTaskClicked(holder));
+        itemView.setOnClickListener(view -> mTaskActionController.launchTask(holder));
         return holder;
     }
 
diff --git a/go/quickstep/src/com/android/quickstep/TaskListLoader.java b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
index e6d1a22..c86c24e 100644
--- a/go/quickstep/src/com/android/quickstep/TaskListLoader.java
+++ b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
@@ -93,6 +93,13 @@
     }
 
     /**
+     * Clears the current task list.
+     */
+    void clearAllTasks() {
+        mTaskList.clear();
+    }
+
+    /**
      * Loads task content for a list of tasks, including the label, icon, and thumbnail. For content
      * that isn't cached, load the content asynchronously in the background.
      *
diff --git a/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java b/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
index 2a53917..98407d8 100644
--- a/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
+++ b/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
@@ -26,11 +26,11 @@
  */
 public final class TaskSwipeCallback extends ItemTouchHelper.SimpleCallback {
 
-    private final TaskInputController mTaskInputController;
+    private final TaskActionController mTaskActionController;
 
-    public TaskSwipeCallback(TaskInputController inputController) {
+    public TaskSwipeCallback(TaskActionController taskActionController) {
         super(0 /* dragDirs */, RIGHT);
-        mTaskInputController = inputController;
+        mTaskActionController = taskActionController;
     }
 
     @Override
@@ -42,7 +42,7 @@
     @Override
     public void onSwiped(ViewHolder viewHolder, int direction) {
         if (direction == RIGHT) {
-            mTaskInputController.onTaskSwiped((TaskHolder) viewHolder);
+            mTaskActionController.removeTask((TaskHolder) viewHolder);
         }
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index 504f640..0e5dbc4 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -36,7 +36,7 @@
 import com.android.launcher3.R;
 import com.android.quickstep.RecentsToActivityHelper;
 import com.android.quickstep.TaskAdapter;
-import com.android.quickstep.TaskInputController;
+import com.android.quickstep.TaskActionController;
 import com.android.quickstep.TaskListLoader;
 import com.android.quickstep.TaskSwipeCallback;
 
@@ -74,19 +74,20 @@
     private final Context mContext;
     private final TaskListLoader mTaskLoader;
     private final TaskAdapter mTaskAdapter;
-    private final TaskInputController mTaskInputController;
+    private final TaskActionController mTaskActionController;
 
     private RecentsToActivityHelper mActivityHelper;
     private RecyclerView mTaskRecyclerView;
     private View mEmptyView;
+    private View mContentView;
 
     public IconRecentsView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
         mTaskLoader = new TaskListLoader(mContext);
         mTaskAdapter = new TaskAdapter(mTaskLoader);
-        mTaskInputController = new TaskInputController(mTaskLoader, mTaskAdapter);
-        mTaskAdapter.setInputController(mTaskInputController);
+        mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter);
+        mTaskAdapter.setActionController(mTaskActionController);
     }
 
     @Override
@@ -98,10 +99,11 @@
             mTaskRecyclerView.setLayoutManager(
                     new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */));
             ItemTouchHelper helper = new ItemTouchHelper(
-                    new TaskSwipeCallback(mTaskInputController));
+                    new TaskSwipeCallback(mTaskActionController));
             helper.attachToRecyclerView(mTaskRecyclerView);
 
             mEmptyView = findViewById(R.id.recent_task_empty_view);
+            mContentView = findViewById(R.id.recent_task_content_view);
             mTaskAdapter.registerAdapterDataObserver(new AdapterDataObserver() {
                 @Override
                 public void onChanged() {
@@ -113,6 +115,9 @@
                     updateContentViewVisibility();
                 }
             });
+
+            View clearAllView = findViewById(R.id.clear_all_button);
+            clearAllView.setOnClickListener(v -> mTaskActionController.clearAllTasks());
         }
     }
 
@@ -162,11 +167,11 @@
     private void updateContentViewVisibility() {
         int taskListSize = mTaskLoader.getCurrentTaskList().size();
         if (mEmptyView.getVisibility() != VISIBLE && taskListSize == 0) {
-            crossfadeViews(mEmptyView, mTaskRecyclerView);
+            crossfadeViews(mEmptyView, mContentView);
             mActivityHelper.leaveRecents();
         }
-        if (mTaskRecyclerView.getVisibility() != VISIBLE && taskListSize > 0) {
-            crossfadeViews(mTaskRecyclerView, mEmptyView);
+        if (mContentView.getVisibility() != VISIBLE && taskListSize > 0) {
+            crossfadeViews(mContentView, mEmptyView);
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
index f0bc223..0924f38 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -173,6 +173,12 @@
         return true;
     }
 
+    public void setCancelWithDeferredScreenshot(boolean deferredWithScreenshot) {
+        if (targetSet != null) {
+            targetSet.controller.setCancelWithDeferredScreenshot(deferredWithScreenshot);
+        }
+    }
+
     public SwipeAnimationTargetSet getController() {
         return targetSet;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index eb1e7b4..e8d4c19 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -1136,6 +1136,7 @@
             mLauncherTransitionController = null;
         }
         mActivityControlHelper.onSwipeUpComplete(mActivity);
+        mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true);
 
         // Animate the first icon.
         mRecentsView.animateUpRunningTaskIconScale(mLiveTileOverlay.cancelIconAnimation());
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
index 62f2183..94e704a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
@@ -38,9 +38,16 @@
  */
 public class RecentsAnimationListenerSet implements RecentsAnimationListener {
 
+    // The actual app surface is replaced by a screenshot upon recents animation cancelation when
+    // deferredWithScreenshot is true. Launcher takes the responsibility to clean up this screenshot
+    // after app transition is finished. This delay is introduced to cover the app transition
+    // period of time.
+    private final int TRANSITION_DELAY = 100;
+
     private final Set<SwipeAnimationListener> mListeners = new ArraySet<>();
     private final boolean mShouldMinimizeSplitScreen;
     private final Consumer<SwipeAnimationTargetSet> mOnFinishListener;
+    private RecentsAnimationControllerCompat mController;
 
     public RecentsAnimationListenerSet(boolean shouldMinimizeSplitScreen,
             Consumer<SwipeAnimationTargetSet> onFinishListener) {
@@ -64,6 +71,7 @@
     public final void onAnimationStart(RecentsAnimationControllerCompat controller,
             RemoteAnimationTargetCompat[] targets, Rect homeContentInsets,
             Rect minimizedHomeBounds) {
+        mController = controller;
         SwipeAnimationTargetSet targetSet = new SwipeAnimationTargetSet(controller, targets,
                 homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen,
                 mOnFinishListener);
@@ -75,12 +83,17 @@
     }
 
     @Override
-    public final void onAnimationCanceled() {
+    public final void onAnimationCanceled(boolean deferredWithScreenshot) {
         Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
             for (SwipeAnimationListener listener : getListeners()) {
                 listener.onRecentsAnimationCanceled();
             }
         });
+        // TODO: handle the transition better instead of simply using a transition delay.
+        if (deferredWithScreenshot) {
+            MAIN_THREAD_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(),
+                    TRANSITION_DELAY);
+        }
     }
 
     private SwipeAnimationListener[] getListeners() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 8f2a2d7..8da1d2b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -74,12 +74,7 @@
 
     @Override
     public void startHome() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            takeScreenshotAndFinishRecentsAnimation(true,
-                    () -> mActivity.getStateManager().goToState(NORMAL));
-        } else {
-            mActivity.getStateManager().goToState(NORMAL);
-        }
+        mActivity.getStateManager().goToState(NORMAL);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 37febf9a..a02df62 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -96,7 +96,6 @@
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
 import com.android.quickstep.util.TaskViewDrawable;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -106,7 +105,6 @@
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.WindowCallbacksCompat;
 
 import java.util.ArrayList;
 import java.util.function.Consumer;
@@ -1608,50 +1606,4 @@
 
         mRecentsAnimationWrapper.finish(toRecents, onFinishComplete);
     }
-
-    public void takeScreenshotAndFinishRecentsAnimation(boolean toRecents,
-            Runnable onFinishComplete) {
-        if (mRecentsAnimationWrapper == null || getRunningTaskView() == null) {
-            if (onFinishComplete != null) {
-                onFinishComplete.run();
-            }
-            return;
-        }
-
-        SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
-        if (controller != null) {
-            // Update the screenshot of the task
-            ThumbnailData taskSnapshot = controller.screenshotTask(mRunningTaskId);
-            TaskView taskView = updateThumbnail(mRunningTaskId, taskSnapshot);
-            if (taskView != null) {
-                taskView.setShowScreenshot(true);
-                // Defer finishing the animation until the next launcher frame with the
-                // new thumbnail
-                new WindowCallbacksCompat(taskView) {
-
-                    // The number of frames to defer until we actually finish the animation
-                    private int mDeferFrameCount = 2;
-
-                    @Override
-                    public void onPostDraw(Canvas canvas) {
-                        if (mDeferFrameCount > 0) {
-                            mDeferFrameCount--;
-                            // Workaround, detach and reattach to invalidate the root node for
-                            // another draw
-                            detach();
-                            attach();
-                            taskView.invalidate();
-                            return;
-                        }
-
-                        detach();
-                        mRecentsAnimationWrapper.finish(toRecents, () -> {
-                            onFinishComplete.run();
-                            mRunningTaskId = -1;
-                        });
-                    }
-                }.attach();
-            }
-        }
-    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
index 682152e..d15a392 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
@@ -208,13 +208,7 @@
                 R.layout.task_view_menu_option, this, false);
         menuOption.setIconAndLabelFor(
                 menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            menuOptionView.setOnClickListener(
-                    view -> mTaskView.getRecentsView().takeScreenshotAndFinishRecentsAnimation(true,
-                            () -> onClickListener.onClick(view)));
-        } else {
-            menuOptionView.setOnClickListener(onClickListener);
-        }
+        menuOptionView.setOnClickListener(onClickListener);
         mOptionLayout.addView(menuOptionView);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 98495db..38aaac5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -261,11 +261,10 @@
             Handler resultCallbackHandler) {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (isRunningTask()) {
-                getRecentsView().finishRecentsAnimation(false,
+                getRecentsView().finishRecentsAnimation(false /* toRecents */,
                         () -> resultCallbackHandler.post(() -> resultCallback.accept(true)));
             } else {
-                getRecentsView().takeScreenshotAndFinishRecentsAnimation(true,
-                        () -> launchTaskInternal(animate, resultCallback, resultCallbackHandler));
+                launchTaskInternal(animate, resultCallback, resultCallbackHandler);
             }
         } else {
             launchTaskInternal(animate, resultCallback, resultCallbackHandler);
diff --git a/tests/Android.mk b/tests/Android.mk
index a787537..bf5cb29 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -23,8 +23,12 @@
 	androidx.annotation_annotation \
 	androidx.test.runner \
 	androidx.test.rules \
-	androidx.test.uiautomator_uiautomator \
-	libSharedSystemUI
+	androidx.test.uiautomator_uiautomator
+
+ifneq (,$(wildcard frameworks/base))
+else
+    LOCAL_STATIC_JAVA_LIBRARIES += libSharedSystemUI
+endif
 
 LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
   ../quickstep/src/com/android/quickstep/SwipeUpSetting.java \
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index d39a38e..fbeb3a2 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.tapl;
 
 import android.graphics.Point;
+import android.os.SystemClock;
 import android.view.MotionEvent;
 import android.widget.TextView;
 
@@ -41,10 +42,12 @@
      */
     public AppIconMenu openMenu() {
         final Point iconCenter = mObject.getVisibleCenter();
-        mLauncher.sendPointer(MotionEvent.ACTION_DOWN, iconCenter);
+        final long downTime = SystemClock.uptimeMillis();
+        mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, iconCenter);
         final UiObject2 deepShortcutsContainer = mLauncher.waitForLauncherObject(
                 "deep_shortcuts_container");
-        mLauncher.sendPointer(MotionEvent.ACTION_UP, iconCenter);
+        mLauncher.sendPointer(
+                downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, iconCenter);
         return new AppIconMenu(mLauncher, deepShortcutsContainer);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 2cde8ec..ef509ed 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -22,6 +22,10 @@
 
 import static org.junit.Assert.assertTrue;
 
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.view.MotionEvent;
+
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.Until;
@@ -33,6 +37,8 @@
  * indicate Launcher as long as Launcher is not in Overview state.
  */
 public class Background extends LauncherInstrumentation.VisibleContainer {
+    private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500;
+    private static final int ZERO_BUTTON_SWIPE_UP_HOLD_DURATION = 400;
 
     Background(LauncherInstrumentation launcher) {
         super(launcher);
@@ -59,19 +65,41 @@
     }
 
     protected void goToOverviewUnchecked(int expectedState) {
-        if (mLauncher.isSwipeUpEnabled()) {
-            final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
-            final int startY = getSwipeStartY();
-            final int swipeHeight = mLauncher.getTestInfo(
-                    getSwipeHeightRequestName()).
-                    getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+        switch (mLauncher.getNavigationModel()) {
+            case ZERO_BUTTON: {
+                final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+                final int startY = getSwipeStartY();
+                final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).
+                        getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+                final Point start = new Point(centerX, startY);
+                final Point end =
+                        new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
 
-            mLauncher.swipe(
-                    centerX, startY, centerX,
-                    startY - swipeHeight - mLauncher.getTouchSlop(),
-                    expectedState);
-        } else {
-            mLauncher.waitForSystemUiObject("recent_apps").click();
+                final long downTime = SystemClock.uptimeMillis();
+                mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start);
+                mLauncher.movePointer(downTime, ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION, start, end);
+                LauncherInstrumentation.sleep(ZERO_BUTTON_SWIPE_UP_HOLD_DURATION);
+                mLauncher.sendPointer(
+                        downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end);
+                break;
+            }
+
+            case TWO_BUTTON: {
+                final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+                final int startY = getSwipeStartY();
+                final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).
+                        getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+
+                mLauncher.swipe(
+                        centerX, startY, centerX,
+                        startY - swipeHeight - mLauncher.getTouchSlop(),
+                        expectedState);
+                break;
+            }
+
+            case THREE_BUTTON:
+                mLauncher.waitForSystemUiObject("recent_apps").click();
+                break;
         }
     }
 
@@ -80,6 +108,6 @@
     }
 
     protected int getSwipeStartY() {
-        return mLauncher.waitForSystemUiObject("home").getVisibleBounds().centerY();
+        return mLauncher.waitForSystemUiObject("navigation_bar_frame").getVisibleBounds().centerY();
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 642735e..234c3bf 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -44,8 +44,10 @@
      * Flings forward (left) and waits the fling's end.
      */
     public void flingForward() {
-        final UiObject2 overview = verifyActiveContainer();
         LauncherInstrumentation.log("Overview.flingForward before fling");
+        final UiObject2 overview = verifyActiveContainer();
+        final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+        overview.setGestureMargins(margin, 0, 0, 0);
         overview.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         mLauncher.waitForIdle();
         verifyActiveContainer();
@@ -71,8 +73,10 @@
      * Flings backward (right) and waits the fling's end.
      */
     public void flingBackward() {
-        final UiObject2 overview = verifyActiveContainer();
         LauncherInstrumentation.log("Overview.flingBackward before fling");
+        final UiObject2 overview = verifyActiveContainer();
+        final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+        overview.setGestureMargins(0, 0, margin, 0);
         overview.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         mLauncher.waitForIdle();
         verifyActiveContainer();
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index e3850ff..f44022b 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.tapl;
 
+import static com.android.launcher3.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
 import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
 
 import android.app.ActivityManager;
@@ -30,14 +31,17 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.util.Log;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.Configurator;
 import androidx.test.uiautomator.UiDevice;
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
@@ -59,6 +63,7 @@
 public final class LauncherInstrumentation {
 
     private static final String TAG = "Tapl";
+    private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
 
     // Types for launcher containers that the user is interacting with. "Background" is a
     // pseudo-container corresponding to inactive launcher covered by another app.
@@ -66,6 +71,8 @@
         WORKSPACE, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND, BASE_OVERVIEW
     }
 
+    public enum NavigationModel {ZERO_BUTTON, TWO_BUTTON, THREE_BUTTON}
+
     // Base class for launcher containers.
     static abstract class VisibleContainer {
         protected final LauncherInstrumentation mLauncher;
@@ -150,7 +157,11 @@
         sActiveContainer = new WeakReference<>(container);
     }
 
-    public boolean isSwipeUpEnabled() {
+    public NavigationModel getNavigationModel() {
+        return isSwipeUpEnabled() ? NavigationModel.TWO_BUTTON : NavigationModel.THREE_BUTTON;
+    }
+
+    private boolean isSwipeUpEnabled() {
         final boolean swipeUpEnabledDefaultValue = SwipeUpSetting.isSwipeUpEnabledDefaultValue();
         return SwipeUpSetting.isSwipeUpSettingAvailable() ?
                 Settings.Secure.getInt(
@@ -286,24 +297,49 @@
         // We need waiting for any accessibility event generated after pressing Home because
         // otherwise waitForIdle may return immediately in case when there was a big enough pause in
         // accessibility events prior to pressing Home.
-        executeAndWaitForEvent(
-                () -> {
-                    log("LauncherInstrumentation.pressHome before clicking");
-                    waitForSystemUiObject("home").click();
-                },
-                event -> true,
-                "Pressing Home didn't produce any events");
-        mDevice.waitForIdle();
+        if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
+            if (hasLauncherObject(WORKSPACE_RES_ID)) {
+                log("0-button pressHome: already in workspace");
+            } else if (hasLauncherObject(OVERVIEW_RES_ID)) {
+                log("0-button pressHome: overview");
+                mDevice.pressHome();
+            } else if (hasLauncherObject(WIDGETS_RES_ID)) {
+                log("0-button pressHome: widgets");
+                mDevice.pressHome();
+            } else if (hasLauncherObject(APPS_RES_ID)) {
+                log("0-button pressHome: all apps");
+                mDevice.pressHome();
+            } else {
+                log("0-button pressHome: another app");
+                assertTrue("Launcher is visible, don't know how to go home",
+                        !mDevice.hasObject(By.pkg(getLauncherPackageName())));
+                final UiObject2 navBar = waitForSystemUiObject("navigation_bar_frame");
 
-        // Temporarily press home twice as the first click sometimes gets ignored  (b/124239413)
-        executeAndWaitForEvent(
-                () -> {
-                    log("LauncherInstrumentation.pressHome before clicking");
-                    waitForSystemUiObject("home").click();
-                },
-                event -> true,
-                "Pressing Home didn't produce any events");
-        mDevice.waitForIdle();
+                swipe(
+                        navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
+                        navBar.getVisibleBounds().centerX(), 0,
+                        BACKGROUND_APP_STATE_ORDINAL, ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
+            }
+        } else {
+            executeAndWaitForEvent(
+                    () -> {
+                        log("LauncherInstrumentation.pressHome before clicking");
+                        waitForSystemUiObject("home").click();
+                    },
+                    event -> true,
+                    "Pressing Home didn't produce any events");
+            mDevice.waitForIdle();
+
+            // Temporarily press home twice as the first click sometimes gets ignored  (b/124239413)
+            executeAndWaitForEvent(
+                    () -> {
+                        log("LauncherInstrumentation.pressHome before clicking");
+                        waitForSystemUiObject("home").click();
+                    },
+                    event -> true,
+                    "Pressing Home didn't produce any events");
+            mDevice.waitForIdle();
+        }
         return getWorkspace();
     }
 
@@ -424,6 +460,11 @@
         return object;
     }
 
+    @Nullable
+    private boolean hasLauncherObject(String resId) {
+        return mDevice.hasObject(getLauncherObjectSelector(resId));
+    }
+
     @NonNull
     UiObject2 waitForLauncherObject(String resName) {
         final BySelector selector = getLauncherObjectSelector(resName);
@@ -446,8 +487,12 @@
     }
 
     void swipe(int startX, int startY, int endX, int endY, int expectedState) {
+        swipe(startX, startY, endX, endY, expectedState, 60);
+    }
+
+    void swipe(int startX, int startY, int endX, int endY, int expectedState, int steps) {
         final Bundle parcel = (Bundle) executeAndWaitForEvent(
-                () -> mDevice.swipe(startX, startY, endX, endY, 60),
+                () -> mDevice.swipe(startX, startY, endX, endY, steps),
                 event -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName()),
                 "Swipe failed to receive an event for the swipe end: " + startX + ", " + startY
                         + ", " + endX + ", " + endY);
@@ -459,13 +504,6 @@
         mDevice.waitForIdle();
     }
 
-    void sendPointer(int action, Point point) {
-        final MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),
-                SystemClock.uptimeMillis(), action, point.x, point.y, 0);
-        mInstrumentation.sendPointerSync(event);
-        event.recycle();
-    }
-
     float getDisplayDensity() {
         return mInstrumentation.getTargetContext().getResources().getDisplayMetrics().density;
     }
@@ -473,4 +511,51 @@
     int getTouchSlop() {
         return ViewConfiguration.get(getContext()).getScaledTouchSlop();
     }
+
+    private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
+            float x, float y) {
+        MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
+        properties.id = 0;
+        properties.toolType = Configurator.getInstance().getToolType();
+
+        MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+        coords.pressure = 1;
+        coords.size = 1;
+        coords.x = x;
+        coords.y = y;
+
+        return MotionEvent.obtain(downTime, eventTime, action, 1,
+                new MotionEvent.PointerProperties[]{properties},
+                new MotionEvent.PointerCoords[]{coords},
+                0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
+    }
+
+    void sendPointer(long downTime, long currentTime, int action, Point point) {
+        final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
+        mInstrumentation.getUiAutomation().injectInputEvent(event, true);
+        event.recycle();
+    }
+
+    void movePointer(long downTime, long duration, Point from, Point to) {
+        final Point point = new Point();
+        for (; ; ) {
+            sleep(16);
+
+            final long currentTime = SystemClock.uptimeMillis();
+            final float progress = (currentTime - downTime) / (float) duration;
+            if (progress > 1) return;
+
+            point.x = from.x + (int) (progress * (to.x - from.x));
+            point.y = from.y + (int) (progress * (to.y - from.y));
+
+            sendPointer(downTime, currentTime, MotionEvent.ACTION_MOVE, point);
+        }
+    }
+
+    static void sleep(int duration) {
+        try {
+            Thread.sleep(duration);
+        } catch (InterruptedException e) {
+        }
+    }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index e3ef74a..7d97acd 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -136,6 +136,8 @@
      */
     public void flingForward() {
         final UiObject2 workspace = verifyActiveContainer();
+        final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+        workspace.setGestureMargins(0, 0, margin, 0);
         workspace.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         mLauncher.waitForIdle();
         verifyActiveContainer();
@@ -147,6 +149,8 @@
      */
     public void flingBackward() {
         final UiObject2 workspace = verifyActiveContainer();
+        final int margin = (int) (50 * mLauncher.getDisplayDensity()) + 1;
+        workspace.setGestureMargins(margin, 0, 0, 0);
         workspace.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
         mLauncher.waitForIdle();
         verifyActiveContainer();