Merge "Fix NPE in OverviewComponentObserver constructor while accessing activityInfo." into ub-launcher3-master
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index 4582243..cbc77d2 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -89,4 +89,6 @@
     public static RotationMode getRotationMode(DeviceProfile dp) {
         return RotationMode.NORMAL;
     }
+
+    public static void clearSwipeSharedState(boolean finishAnimation) {}
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index 8d5ac50..6ecf1c1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -184,6 +185,13 @@
     }
 
     /**
+     * Clears the swipe shared state for the current swipe gesture.
+     */
+    public static void clearSwipeSharedState(boolean finishAnimation) {
+        TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation);
+    }
+
+    /**
      * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
      *
      * @param launcher the launcher activity
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
index 6689ce3..c55f656 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
@@ -72,13 +72,15 @@
         mLastAnimationRunning = false;
     }
 
-    private void clearListenerState() {
+    private void clearListenerState(boolean finishAnimation) {
         if (mRecentsAnimationListener != null) {
             mRecentsAnimationListener.removeListener(this);
             mRecentsAnimationListener.cancelListener();
             if (mLastAnimationRunning && mLastAnimationTarget != null) {
                 Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(),
-                        mLastAnimationTarget::cancelAnimation);
+                        finishAnimation
+                                ? mLastAnimationTarget::finishAnimation
+                                : mLastAnimationTarget::cancelAnimation);
                 mLastAnimationTarget = null;
             }
         }
@@ -106,7 +108,7 @@
             }
         }
 
-        clearListenerState();
+        clearListenerState(false /* finishAnimation */);
         boolean shouldMinimiseSplitScreen = mOverviewComponentObserver == null ? false
                 : mOverviewComponentObserver.getActivityControlHelper().shouldMinimizeSplitScreen();
         mRecentsAnimationListener = new RecentsAnimationListenerSet(
@@ -138,8 +140,8 @@
         mLastAnimationTarget = mLastAnimationTarget.cloneWithoutTargets();
     }
 
-    public void clearAllState() {
-        clearListenerState();
+    public void clearAllState(boolean finishAnimation) {
+        clearListenerState(finishAnimation);
         canGestureBeContinued = false;
         recentsAnimationFinishInterrupted = false;
         nextRunningTaskId = -1;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 22ebe61..9353c04 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -74,6 +74,7 @@
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.logging.EventLogArray;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.SysUINavigationMode.Mode;
@@ -81,6 +82,7 @@
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.AssistantTouchConsumer;
 import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
+import com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer;
 import com.android.quickstep.inputconsumers.InputConsumer;
 import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
@@ -224,14 +226,18 @@
     };
 
     private static boolean sConnected = false;
+    private static final SwipeSharedState sSwipeSharedState = new SwipeSharedState();
 
     public static boolean isConnected() {
         return sConnected;
     }
 
-    private final SwipeSharedState mSwipeSharedState = new SwipeSharedState();
+    public static SwipeSharedState getSwipeSharedState() {
+        return sSwipeSharedState;
+    }
+
     private final InputConsumer mResetGestureInputConsumer =
-            new ResetGestureInputConsumer(mSwipeSharedState);
+            new ResetGestureInputConsumer(sSwipeSharedState);
 
     private ActivityManagerWrapper mAM;
     private RecentsModel mRecentsModel;
@@ -435,7 +441,7 @@
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
         mIsUserUnlocked = true;
 
-        mSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver);
+        sSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver);
         mInputConsumer.registerInputConsumer();
         onSystemUiProxySet();
         onSystemUiFlagsChanged();
@@ -497,6 +503,9 @@
     }
 
     private void onInputEvent(InputEvent ev) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.EVENTS_TO_OVERVIEW_MISSING_TAG, "onInputEvent " + ev);
+        }
         if (!(ev instanceof MotionEvent)) {
             Log.e(TAG, "Unknown event " + ev);
             return;
@@ -588,7 +597,7 @@
     private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) {
         final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
         if (!useSharedState) {
-            mSwipeSharedState.clearAllState();
+            sSwipeSharedState.clearAllState(false /* finishAnimation */);
         }
         if ((mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0) {
             // This handles apps showing over the lockscreen (e.g. camera)
@@ -598,22 +607,26 @@
         final ActivityControlHelper activityControl =
                 mOverviewComponentObserver.getActivityControlHelper();
 
-        if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher
-                && !mSwipeSharedState.recentsAnimationFinishInterrupted) {
+        if (runningTaskInfo == null && !sSwipeSharedState.goingToLauncher
+                && !sSwipeSharedState.recentsAnimationFinishInterrupted) {
             return mResetGestureInputConsumer;
-        } else if (mSwipeSharedState.recentsAnimationFinishInterrupted) {
+        } else if (sSwipeSharedState.recentsAnimationFinishInterrupted) {
             // If the finish animation was interrupted, then continue using the other activity input
             // consumer but with the next task as the running task
             RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
-            info.id = mSwipeSharedState.nextRunningTaskId;
+            info.id = sSwipeSharedState.nextRunningTaskId;
             return createOtherActivityInputConsumer(event, info);
-        } else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
+        } else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
             return createOverviewInputConsumer(event);
         } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityControl.isInLiveTileMode()) {
             return createOverviewInputConsumer(event);
         } else if (mGestureBlockingActivity != null && runningTaskInfo != null
                 && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
             return mResetGestureInputConsumer;
+        } else if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
+            return new FallbackNoButtonInputConsumer(this, activityControl,
+                    mInputMonitorCompat, sSwipeSharedState, mSwipeTouchRegion,
+                    mOverviewComponentObserver, disableHorizontalSwipe(event), runningTaskInfo);
         } else {
             return createOtherActivityInputConsumer(event, runningTaskInfo);
         }
@@ -635,13 +648,13 @@
         return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
                 mOverviewComponentObserver.getOverviewIntent(), activityControl,
                 shouldDefer, mOverviewCallbacks, mInputConsumer, this::onConsumerInactive,
-                mSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion,
+                sSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion,
                 disableHorizontalSwipe(event));
     }
 
     private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) {
         if (mMode == Mode.NO_BUTTON && taskInfo != null) {
-            return new DeviceLockedInputConsumer(this, mSwipeSharedState, mInputMonitorCompat,
+            return new DeviceLockedInputConsumer(this, sSwipeSharedState, mInputMonitorCompat,
                     mSwipeTouchRegion, taskInfo.taskId);
         } else {
             return mResetGestureInputConsumer;
@@ -656,7 +669,7 @@
             return mResetGestureInputConsumer;
         }
 
-        if (activity.getRootView().hasWindowFocus() || mSwipeSharedState.goingToLauncher) {
+        if (activity.getRootView().hasWindowFocus() || sSwipeSharedState.goingToLauncher) {
             return new OverviewInputConsumer(activity, mInputMonitorCompat,
                     false /* startingInActivityBounds */);
         } else {
@@ -703,7 +716,7 @@
                     + mOverviewComponentObserver.getActivityControlHelper().isResumed());
             pw.println("  useSharedState=" + mConsumer.useSharedSwipeState());
             if (mConsumer.useSharedSwipeState()) {
-                mSwipeSharedState.dump("    ", pw);
+                sSwipeSharedState.dump("    ", pw);
             }
             pw.println("  mConsumer=" + mConsumer.getName());
             pw.println("FeatureFlags:");
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
new file mode 100644
index 0000000..d05ca2a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.inputconsumers;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
+import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.quickstep.WindowTransformSwipeHandler.MIN_SWIPE_DURATION;
+import static com.android.quickstep.inputconsumers.OtherActivityInputConsumer.QUICKSTEP_TOUCH_SLOP_RATIO;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.R;
+import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.SwipeSharedState;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
+import com.android.quickstep.util.NavBarPosition;
+import com.android.quickstep.util.RecentsAnimationListenerSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.InputMonitorCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+public class FallbackNoButtonInputConsumer implements InputConsumer, SwipeAnimationListener {
+
+    private static final int STATE_NOT_FINISHED = 0;
+    private static final int STATE_FINISHED_TO_HOME = 1;
+    private static final int STATE_FINISHED_TO_APP = 2;
+
+    private static final float PROGRESS_TO_END_GESTURE = -2;
+
+    private final ActivityControlHelper mActivityControlHelper;
+    private final InputMonitorCompat mInputMonitor;
+    private final Context mContext;
+    private final NavBarPosition mNavBarPosition;
+    private final SwipeSharedState mSwipeSharedState;
+    private final OverviewComponentObserver mOverviewComponentObserver;
+    private final int mRunningTaskId;
+
+    private final ClipAnimationHelper mClipAnimationHelper;
+    private final TransformParams mTransformParams = new TransformParams();
+    private final float mTransitionDragLength;
+    private final DeviceProfile mDP;
+
+    private final RectF mSwipeTouchRegion;
+    private final boolean mDisableHorizontalSwipe;
+
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+
+    private int mActivePointerId = -1;
+    // Slop used to determine when we say that the gesture has started.
+    private boolean mPassedPilferInputSlop;
+
+    private VelocityTracker mVelocityTracker;
+
+    // Distance after which we start dragging the window.
+    private final float mTouchSlop;
+
+    // Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
+    private float mStartDisplacement;
+    private SwipeAnimationTargetSet mSwipeAnimationTargetSet;
+    private float mProgress;
+
+    private int mState = STATE_NOT_FINISHED;
+
+    public FallbackNoButtonInputConsumer(Context context,
+            ActivityControlHelper activityControlHelper, InputMonitorCompat inputMonitor,
+            SwipeSharedState swipeSharedState, RectF swipeTouchRegion,
+            OverviewComponentObserver overviewComponentObserver,
+            boolean disableHorizontalSwipe, RunningTaskInfo runningTaskInfo) {
+        mContext = context;
+        mActivityControlHelper = activityControlHelper;
+        mInputMonitor = inputMonitor;
+        mOverviewComponentObserver = overviewComponentObserver;
+        mRunningTaskId = runningTaskInfo.id;
+
+        mSwipeSharedState = swipeSharedState;
+        mSwipeTouchRegion = swipeTouchRegion;
+        mDisableHorizontalSwipe = disableHorizontalSwipe;
+
+        mNavBarPosition = new NavBarPosition(context);
+        mVelocityTracker = VelocityTracker.obtain();
+
+        mTouchSlop = QUICKSTEP_TOUCH_SLOP_RATIO
+                * ViewConfiguration.get(context).getScaledTouchSlop();
+
+        mClipAnimationHelper = new ClipAnimationHelper(context);
+
+        mDP = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context);
+        Rect tempRect = new Rect();
+        mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
+                mDP, context, tempRect);
+        mClipAnimationHelper.updateTargetRect(tempRect);
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_FALLBACK_NO_BUTTON;
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        if (mVelocityTracker == null) {
+            return;
+        }
+
+        mVelocityTracker.addMovement(ev);
+        if (ev.getActionMasked() == ACTION_POINTER_UP) {
+            mVelocityTracker.clear();
+        }
+
+        switch (ev.getActionMasked()) {
+            case ACTION_DOWN: {
+                mActivePointerId = ev.getPointerId(0);
+                mDownPos.set(ev.getX(), ev.getY());
+                mLastPos.set(mDownPos);
+                break;
+            }
+            case ACTION_POINTER_DOWN: {
+                if (!mPassedPilferInputSlop) {
+                    // Cancel interaction in case of multi-touch interaction
+                    int ptrIdx = ev.getActionIndex();
+                    if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
+                        forceCancelGesture(ev);
+                    }
+                }
+                break;
+            }
+            case ACTION_POINTER_UP: {
+                int ptrIdx = ev.getActionIndex();
+                int ptrId = ev.getPointerId(ptrIdx);
+                if (ptrId == mActivePointerId) {
+                    final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+                    mDownPos.set(
+                            ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+                            ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+                    mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+                    mActivePointerId = ev.getPointerId(newPointerIdx);
+                }
+                break;
+            }
+            case ACTION_MOVE: {
+                int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                if (pointerIndex == INVALID_POINTER_ID) {
+                    break;
+                }
+                mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+                float displacement = getDisplacement(ev);
+
+                if (!mPassedPilferInputSlop) {
+                    if (mDisableHorizontalSwipe && Math.abs(mLastPos.x - mDownPos.x)
+                            > Math.abs(mLastPos.y - mDownPos.y)) {
+                        // Horizontal gesture is not allowed in this region
+                        forceCancelGesture(ev);
+                        break;
+                    }
+
+                    if (Math.abs(displacement) >= mTouchSlop) {
+                        mPassedPilferInputSlop = true;
+
+                        // Deferred gesture, start the animation and gesture tracking once
+                        // we pass the actual touch slop
+                        startTouchTrackingForWindowAnimation(displacement);
+                    }
+                } else {
+                    updateDisplacement(displacement - mStartDisplacement);
+                }
+                break;
+            }
+            case ACTION_CANCEL:
+            case ACTION_UP: {
+                finishTouchTracking(ev);
+                break;
+            }
+        }
+    }
+
+    private void startTouchTrackingForWindowAnimation(float displacement) {
+        mStartDisplacement = Math.min(displacement, -mTouchSlop);
+
+        RecentsAnimationListenerSet listenerSet =
+                mSwipeSharedState.newRecentsAnimationListenerSet();
+        listenerSet.addListener(this);
+        Intent homeIntent = mOverviewComponentObserver.getHomeIntent();
+        BackgroundExecutor.get().submit(
+                () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
+                        homeIntent, null, listenerSet, null, null));
+
+        ActivityManagerWrapper.getInstance().closeSystemWindows(
+                CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+        mInputMonitor.pilferPointers();
+    }
+
+    private void updateDisplacement(float displacement) {
+        mProgress = displacement / mTransitionDragLength;
+        mTransformParams.setProgress(mProgress);
+
+        if (mSwipeAnimationTargetSet != null) {
+            mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
+        }
+    }
+
+    private void forceCancelGesture(MotionEvent ev) {
+        int action = ev.getAction();
+        ev.setAction(ACTION_CANCEL);
+        finishTouchTracking(ev);
+        ev.setAction(action);
+    }
+
+    /**
+     * Called when the gesture has ended. Does not correlate to the completion of the interaction as
+     * the animation can still be running.
+     */
+    private void finishTouchTracking(MotionEvent ev) {
+        if (ev.getAction() == ACTION_CANCEL) {
+            mState = STATE_FINISHED_TO_APP;
+        } else {
+            mVelocityTracker.computeCurrentVelocity(1000,
+                    ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
+            float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
+            float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
+            float velocity = mNavBarPosition.isRightEdge() ? velocityX
+                    : mNavBarPosition.isLeftEdge() ? -velocityX
+                            : velocityY;
+            float flingThreshold = mContext.getResources()
+                    .getDimension(R.dimen.quickstep_fling_threshold_velocity);
+            boolean isFling = Math.abs(velocity) > flingThreshold;
+
+            boolean goingHome;
+            if (!isFling) {
+                goingHome = -mProgress >= MIN_PROGRESS_FOR_OVERVIEW;
+            } else {
+                goingHome = velocity < 0;
+            }
+
+            if (goingHome) {
+                mState = STATE_FINISHED_TO_HOME;
+            } else {
+                mState = STATE_FINISHED_TO_APP;
+            }
+        }
+
+        if (mSwipeAnimationTargetSet != null) {
+            finishAnimationTargetSet();
+        }
+    }
+
+    private void finishAnimationTargetSet() {
+        if (mState == STATE_FINISHED_TO_APP) {
+            mSwipeAnimationTargetSet.finishController(false, null, false);
+        } else {
+            if (mProgress < PROGRESS_TO_END_GESTURE) {
+                mSwipeAnimationTargetSet.finishController(true, null, true);
+            } else {
+                long duration = (long) (Math.min(mProgress - PROGRESS_TO_END_GESTURE, 1)
+                        * MAX_SWIPE_DURATION / Math.abs(PROGRESS_TO_END_GESTURE));
+                if (duration < 0) {
+                    duration = MIN_SWIPE_DURATION;
+                }
+
+                ValueAnimator anim = ValueAnimator.ofFloat(mProgress, PROGRESS_TO_END_GESTURE);
+                anim.addUpdateListener(a -> {
+                    float p = (Float) anim.getAnimatedValue();
+                    mTransformParams.setProgress(p);
+                    mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
+                });
+                anim.setDuration(duration);
+                anim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mSwipeAnimationTargetSet.finishController(true, null, true);
+                    }
+                });
+                anim.start();
+            }
+        }
+    }
+
+    @Override
+    public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
+        mSwipeAnimationTargetSet = targetSet;
+        Rect overviewStackBounds = new Rect(0, 0, mDP.widthPx, mDP.heightPx);
+        RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
+
+        mDP.updateIsSeascape(mContext.getSystemService(WindowManager.class));
+        if (runningTaskTarget != null) {
+            mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
+        }
+        mClipAnimationHelper.prepareAnimation(mDP, false /* isOpening */);
+
+        overviewStackBounds
+                .inset(-overviewStackBounds.width() / 5, -overviewStackBounds.height() / 5);
+        mClipAnimationHelper.updateTargetRect(overviewStackBounds);
+        mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
+
+        if (mState != STATE_NOT_FINISHED) {
+            finishAnimationTargetSet();
+        }
+    }
+
+    @Override
+    public void onRecentsAnimationCanceled() { }
+
+    private float getDisplacement(MotionEvent ev) {
+        if (mNavBarPosition.isRightEdge()) {
+            return ev.getX() - mDownPos.x;
+        } else if (mNavBarPosition.isLeftEdge()) {
+            return mDownPos.x - ev.getX();
+        } else {
+            return ev.getY() - mDownPos.y;
+        }
+    }
+
+    @Override
+    public boolean allowInterceptByParent() {
+        return !mPassedPilferInputSlop;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
index a1e5d47..f5cf654 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
@@ -33,6 +33,7 @@
     int TYPE_SCREEN_PINNED = 1 << 6;
     int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
     int TYPE_RESET_GESTURE = 1 << 8;
+    int TYPE_FALLBACK_NO_BUTTON = 1 << 9;
 
     String[] NAMES = new String[] {
            "TYPE_NO_OP",                    // 0
@@ -44,6 +45,7 @@
             "TYPE_SCREEN_PINNED",           // 6
             "TYPE_OVERVIEW_WITHOUT_FOCUS",  // 7
             "TYPE_RESET_GESTURE",           // 8
+            "TYPE_FALLBACK_NO_BUTTON",      // 9
     };
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 6bc543f..4c137d3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -79,7 +79,7 @@
     private static final String UP_EVT = "OtherActivityInputConsumer.UP";
 
     // TODO: Move to quickstep contract
-    private static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
+    public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
 
     private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
     private final RunningTaskInfo mRunningTask;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 56db209..f2e53bb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -19,6 +19,7 @@
 import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
@@ -26,6 +27,7 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.OverviewCallbacks;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -80,6 +82,9 @@
 
     @Override
     public void onMotionEvent(MotionEvent ev) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.EVENTS_TO_OVERVIEW_MISSING_TAG, "onMotionEvent " + ev);
+        }
         if (!mProxyTouch) {
             return;
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
index 56cba21..8eede81 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
@@ -39,7 +39,7 @@
     public void onMotionEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN
                 && mSwipeSharedState.getActiveListener() != null) {
-            mSwipeSharedState.clearAllState();
+            mSwipeSharedState.clearAllState(false /* finishAnimation */);
         }
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
index df9efa2..381c27a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
@@ -106,6 +106,10 @@
         finishController(false /* toRecents */, null, false /* sendUserLeaveHint */);
     }
 
+    public void finishAnimation() {
+        finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
+    }
+
     public interface SwipeAnimationListener {
 
         void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet);
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 e7e4189..b26fdce 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
@@ -368,6 +368,9 @@
         } else {
             mSnapshotView.setThumbnail(null, null);
             setIcon(null);
+            // Reset the task thumbnail reference as well (it will be fetched from the cache or
+            // reloaded next time we need it)
+            mTask.thumbnail = null;
         }
     }
 
@@ -488,9 +491,6 @@
         mSnapshotView.setThumbnail(mTask, null);
         setOverlayEnabled(false);
         onTaskListVisibilityChanged(false);
-        if (mTask != null) {
-            mTask.thumbnail = null;
-        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 8643160..44324cb 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -765,7 +765,7 @@
                 LauncherAnimationRunner.AnimationResult result) {
             if (!mLauncher.hasBeenResumed()) {
                 // If launcher is not resumed, wait until new async-frame after resume
-                mLauncher.setOnResumeCallback(() ->
+                mLauncher.addOnResumeCallback(() ->
                         postAsyncCallback(mHandler, () ->
                                 onCreateAnimation(targetCompats, result)));
                 return;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index fee1820..f5ba372 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -17,17 +17,22 @@
 
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.MotionEvent.ACTION_CANCEL;
 
+import android.graphics.PointF;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
+import android.view.Window;
+import android.view.WindowManager;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.touch.TouchEventTranslator;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.RecentsModel;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -36,18 +41,29 @@
 
 /**
  * TouchController for handling touch events that get sent to the StatusBar. Once the
- * Once the event delta y passes the touch slop, the events start getting forwarded.
+ * Once the event delta mDownY passes the touch slop, the events start getting forwarded.
  * All events are offset by initial Y value of the pointer.
  */
 public class StatusBarTouchController implements TouchController {
 
     private static final String TAG = "StatusBarController";
 
+    /**
+     * Window flag: Enable touches to slide out of a window into neighboring
+     * windows in mid-gesture instead of being captured for the duration of
+     * the gesture.
+     *
+     * This flag changes the behavior of touch focus for this window only.
+     * Touches can slide out of the window but they cannot necessarily slide
+     * back in (unless the other window with touch focus permits it).
+     */
+    private static final int FLAG_SLIPPERY = 0x20000000;
+
     protected final Launcher mLauncher;
-    protected final TouchEventTranslator mTranslator;
     private final float mTouchSlop;
     private ISystemUiProxy mSysUiProxy;
     private int mLastAction;
+    private final SparseArray<PointF> mDownEvents;
 
     /* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/
     private boolean mCanIntercept;
@@ -56,7 +72,7 @@
         mLauncher = l;
         // Guard against TAPs by increasing the touch slop.
         mTouchSlop = 2 * ViewConfiguration.get(l).getScaledTouchSlop();
-        mTranslator = new TouchEventTranslator((MotionEvent ev)-> dispatchTouchEvent(ev));
+        mDownEvents = new SparseArray<>();
     }
 
     @Override
@@ -64,7 +80,6 @@
         writer.println(prefix + "mCanIntercept:" + mCanIntercept);
         writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction));
         writer.println(prefix + "mSysUiProxy available:" + (mSysUiProxy != null));
-
     }
 
     private void dispatchTouchEvent(MotionEvent ev) {
@@ -81,26 +96,31 @@
     @Override
     public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         int action = ev.getActionMasked();
+        int idx = ev.getActionIndex();
+        int pid = ev.getPointerId(idx);
         if (action == ACTION_DOWN) {
             mCanIntercept = canInterceptTouch(ev);
             if (!mCanIntercept) {
                 return false;
             }
-            mTranslator.reset();
-            mTranslator.setDownParameters(0, ev);
+            mDownEvents.put(pid, new PointF(ev.getX(), ev.getY()));
         } else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
-            // Check!! should only set it only when threshold is not entered.
-            mTranslator.setDownParameters(ev.getActionIndex(), ev);
+           // Check!! should only set it only when threshold is not entered.
+           mDownEvents.put(pid, new PointF(ev.getX(idx), ev.getY(idx)));
         }
         if (!mCanIntercept) {
             return false;
         }
         if (action == ACTION_MOVE) {
-            float dy = ev.getY() - mTranslator.getDownY();
-            float dx = ev.getX() - mTranslator.getDownX();
-            if (dy > mTouchSlop && dy > Math.abs(dx)) {
-                mTranslator.dispatchDownEvents(ev);
-                mTranslator.processMotionEvent(ev);
+            float dy = ev.getY(idx) - mDownEvents.get(pid).y;
+            float dx = ev.getX(idx) - mDownEvents.get(pid).x;
+            // Currently input dispatcher will not do touch transfer if there are more than
+            // one touch pointer. Hence, even if slope passed, only set the slippery flag
+            // when there is single touch event. (context: InputDispatcher.cpp line 1445)
+            if (dy > mTouchSlop && dy > Math.abs(dx) && ev.getPointerCount() == 1) {
+                ev.setAction(ACTION_DOWN);
+                dispatchTouchEvent(ev);
+                setWindowSlippery(true);
                 return true;
             }
             if (Math.abs(dx) > mTouchSlop) {
@@ -110,13 +130,27 @@
         return false;
     }
 
-
     @Override
     public final boolean onControllerTouchEvent(MotionEvent ev) {
-        mTranslator.processMotionEvent(ev);
+        if (ev.getAction() == ACTION_UP || ev.getAction() == ACTION_CANCEL) {
+            dispatchTouchEvent(ev);
+            setWindowSlippery(false);
+            return true;
+        }
         return true;
     }
 
+    private void setWindowSlippery(boolean enable) {
+        Window w = mLauncher.getWindow();
+        WindowManager.LayoutParams wlp = w.getAttributes();
+        if (enable) {
+            wlp.flags |= FLAG_SLIPPERY;
+        } else {
+            wlp.flags &= ~FLAG_SLIPPERY;
+        }
+        w.setAttributes(wlp);
+    }
+
     private boolean canInterceptTouch(MotionEvent ev) {
         if (!mLauncher.isInState(LauncherState.NORMAL) ||
                 AbstractFloatingView.getTopOpenViewWithType(mLauncher,
@@ -132,4 +166,4 @@
         mSysUiProxy = RecentsModel.INSTANCE.get(mLauncher).getSystemUiProxy();
         return mSysUiProxy != null;
     }
-}
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0a73b8b..0738aff 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -33,7 +33,6 @@
 
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
-import com.android.systemui.shared.system.QuickStepContract;
 import java.util.ArrayList;
 
 /**
@@ -58,7 +57,9 @@
     private String mUpdateRegisteredPackage;
     private ActivityControlHelper mActivityControlHelper;
     private Intent mOverviewIntent;
+    private Intent mHomeIntent;
     private int mSystemUiStateFlags;
+    private boolean mIsHomeAndOverviewSame;
 
     public OverviewComponentObserver(Context context) {
         mContext = context;
@@ -93,11 +94,14 @@
 
         final String overviewIntentCategory;
         ComponentName overviewComponent;
+        mHomeIntent = null;
+
         if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 &&
                 (defaultHome == null || mMyHomeComponent.equals(defaultHome))) {
             // User default home is same as out home app. Use Overview integrated in Launcher.
             overviewComponent = mMyHomeComponent;
             mActivityControlHelper = new LauncherActivityControllerHelper();
+            mIsHomeAndOverviewSame = true;
             overviewIntentCategory = Intent.CATEGORY_HOME;
 
             if (mUpdateRegisteredPackage != null) {
@@ -109,8 +113,12 @@
             // The default home app is a different launcher. Use the fallback Overview instead.
             overviewComponent = new ComponentName(mContext, RecentsActivity.class);
             mActivityControlHelper = new FallbackActivityControllerHelper();
+            mIsHomeAndOverviewSame = false;
             overviewIntentCategory = Intent.CATEGORY_DEFAULT;
 
+            mHomeIntent = new Intent(Intent.ACTION_MAIN)
+                    .addCategory(Intent.CATEGORY_HOME)
+                    .setComponent(defaultHome);
             // User's default home app can change as a result of package updates of this app (such
             // as uninstalling the app or removing the "Launcher" feature in an update).
             // Listen for package updates of this app (and remove any previously attached
@@ -135,6 +143,9 @@
                 .addCategory(overviewIntentCategory)
                 .setComponent(overviewComponent)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        if (mHomeIntent == null) {
+            mHomeIntent = mOverviewIntent;
+        }
     }
 
     /**
@@ -159,6 +170,20 @@
     }
 
     /**
+     * Get the current intent for going to the home activity.
+     */
+    public Intent getHomeIntent() {
+        return mHomeIntent;
+    }
+
+    /**
+     * Returns true if home and overview are same activity.
+     */
+    public boolean isHomeAndOverviewSame() {
+        return mIsHomeAndOverviewSame;
+    }
+
+    /**
      * Get the current activity control helper for managing interactions to the overview activity.
      *
      * @return the current activity control helper
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 3538373..f27ba85 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -84,7 +84,7 @@
         final int requestLoadId = mChangeId;
         Runnable resultCallback = callback == null
                 ? () -> { }
-                : () -> callback.accept(mTasks);
+                : () -> callback.accept(copyOf(mTasks));
 
         if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) {
             // The list is up to date, callback with the same list
@@ -183,4 +183,14 @@
 
         return allTasks;
     }
+
+    private ArrayList<Task> copyOf(ArrayList<Task> tasks) {
+        ArrayList<Task> newTasks = new ArrayList<>();
+        for (int i = 0; i < tasks.size(); i++) {
+            Task t = tasks.get(i);
+            newTasks.add(new Task(t.key, t.colorPrimary, t.colorBackground, t.isDockable,
+                    t.isLocked, t.taskDescription, t.topActivity));
+        }
+        return newTasks;
+    }
 }
\ No newline at end of file
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 0135911..e5f949b 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -43,10 +43,12 @@
 
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.junit.runners.model.Statement;
@@ -62,10 +64,14 @@
     private final LauncherInstrumentation mLauncher;
     private final ActivityInfo mOtherLauncherActivity;
 
-    @Rule public final TestRule mDisableHeadsUpNotification = disableHeadsUpNotification();
-    @Rule public final TestRule mQuickstepOnOffExecutor;
+    @Rule
+    public final TestRule mDisableHeadsUpNotification = disableHeadsUpNotification();
 
-    @Rule public final TestRule mSetLauncherCommand;
+    @Rule
+    public final TestRule mSetLauncherCommand;
+
+    @Rule
+    public final TestRule mOrderSensitiveRules;
 
     public FallbackRecentsTest() throws RemoteException {
         Instrumentation instrumentation = getInstrumentation();
@@ -74,7 +80,10 @@
         mDevice.setOrientationNatural();
         mLauncher = new LauncherInstrumentation(instrumentation);
 
-        mQuickstepOnOffExecutor = new NavigationModeSwitchRule(mLauncher);
+        mOrderSensitiveRules = RuleChain.
+                outerRule(new NavigationModeSwitchRule(mLauncher)).
+                around(new FailureWatcher(mDevice));
+
         mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
                 getHomeIntentInPackage(context),
                 MATCH_DISABLED_COMPONENTS).get(0).activityInfo;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ad2783e..d9af4da 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -244,7 +244,7 @@
 
     @Thunk boolean mWorkspaceLoading = true;
 
-    private OnResumeCallback mOnResumeCallback;
+    private ArrayList<OnResumeCallback> mOnResumeCallbacks = new ArrayList<>();
 
     private ViewOnDrawExecutor mPendingExecutor;
 
@@ -869,6 +869,7 @@
     @Override
     protected void onStop() {
         super.onStop();
+
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStop();
         }
@@ -951,7 +952,10 @@
         mHandler.removeCallbacks(mHandleDeferredResume);
         Utilities.postAsyncCallback(mHandler, mHandleDeferredResume);
 
-        setOnResumeCallback(null);
+        for (OnResumeCallback cb : mOnResumeCallbacks) {
+            cb.onLauncherResume();
+        }
+        mOnResumeCallbacks.clear();
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onResume();
@@ -1805,6 +1809,16 @@
             android.util.Log.d(TestProtocol.NO_START_TAG,
                     "startActivitySafely outer");
         }
+
+        if (!hasBeenResumed()) {
+            // Workaround an issue where the WM launch animation is clobbered when finishing the
+            // recents animation into launcher. Defer launching the activity until Launcher is
+            // next resumed.
+            addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
+            UiFactory.clearSwipeSharedState(true /* finishAnimation */);
+            return true;
+        }
+
         boolean success = super.startActivitySafely(v, intent, item, sourceContainer);
         if (success && v instanceof BubbleTextView) {
             // This is set to the view that launched the activity that navigated the user away
@@ -1813,7 +1827,7 @@
             // state when we return to launcher.
             BubbleTextView btv = (BubbleTextView) v;
             btv.setStayPressed(true);
-            setOnResumeCallback(btv);
+            addOnResumeCallback(btv);
         }
         return success;
     }
@@ -1861,11 +1875,8 @@
         return result;
     }
 
-    public void setOnResumeCallback(OnResumeCallback callback) {
-        if (mOnResumeCallback != null) {
-            mOnResumeCallback.onLauncherResume();
-        }
-        mOnResumeCallback = callback;
+    public void addOnResumeCallback(OnResumeCallback callback) {
+        mOnResumeCallbacks.add(callback);
     }
 
     /**
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 0cf6e44..55cb6f2 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -179,7 +179,7 @@
             DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource;
             if (target != null) {
                 deferred.mPackageName = target.getPackageName();
-                mLauncher.setOnResumeCallback(deferred);
+                mLauncher.addOnResumeCallback(deferred);
             } else {
                 deferred.sendFailure();
             }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 6ffc2d9..3774042 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -78,4 +78,5 @@
     public static final String NO_START_TAG = "b/132900132";
     public static final String NO_START_TASK_TAG = "b/133765434";
     public static final String NO_OVERVIEW_EVENT_TAG = "b/134532571";
+    public static final String EVENTS_TO_OVERVIEW_MISSING_TAG = "b/133867119";
 }
diff --git a/src/com/android/launcher3/touch/TouchEventTranslator.java b/src/com/android/launcher3/touch/TouchEventTranslator.java
deleted file mode 100644
index 3fcda90..0000000
--- a/src/com/android/launcher3/touch/TouchEventTranslator.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.touch;
-
-import android.graphics.PointF;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
-
-import java.util.function.Consumer;
-
-/**
- * To minimize the size of the MotionEvent, historic events are not copied and passed via the
- * listener.
- */
-public class TouchEventTranslator {
-
-    private static final String TAG = "TouchEventTranslator";
-    private static final boolean DEBUG = false;
-
-    private class DownState {
-        long timeStamp;
-        float downX;
-        float downY;
-        public DownState(long timeStamp, float downX, float downY) {
-            this.timeStamp = timeStamp;
-            this.downX = downX;
-            this.downY = downY;
-        }
-    };
-    private final DownState ZERO = new DownState(0, 0f, 0f);
-
-    private final Consumer<MotionEvent> mListener;
-
-    private final SparseArray<DownState> mDownEvents;
-    private final SparseArray<PointF> mFingers;
-
-    private final SparseArray<Pair<PointerProperties[], PointerCoords[]>> mCache;
-
-    public TouchEventTranslator(Consumer<MotionEvent> listener) {
-        mDownEvents = new SparseArray<>();
-        mFingers = new SparseArray<>();
-        mCache = new SparseArray<>();
-
-        mListener = listener;
-    }
-
-    public void reset() {
-        mDownEvents.clear();
-        mFingers.clear();
-    }
-
-    public float getDownX() {
-        return mDownEvents.get(0).downX;
-    }
-
-    public float getDownY() {
-        return mDownEvents.get(0).downY;
-    }
-
-    public void setDownParameters(int idx, MotionEvent e) {
-        DownState ev = new DownState(e.getEventTime(), e.getX(idx), e.getY(idx));
-        mDownEvents.append(idx, ev);
-    }
-
-    public void dispatchDownEvents(MotionEvent ev) {
-        for(int i = 0; i < ev.getPointerCount() && i < mDownEvents.size(); i++) {
-            int pid = ev.getPointerId(i);
-            put(pid, i, ev.getX(i), 0, mDownEvents.get(i).timeStamp, ev);
-        }
-    }
-
-    public void processMotionEvent(MotionEvent ev) {
-        if (DEBUG) {
-            printSamples(TAG + " processMotionEvent", ev);
-        }
-        int index = ev.getActionIndex();
-        float x = ev.getX(index);
-        float y = ev.getY(index) - mDownEvents.get(index, ZERO).downY;
-        switch (ev.getActionMasked()) {
-            case MotionEvent.ACTION_POINTER_DOWN:
-                int pid = ev.getPointerId(index);
-                if(mFingers.get(pid, null) != null) {
-                    for(int i=0; i < ev.getPointerCount(); i++) {
-                        pid = ev.getPointerId(i);
-                        position(pid, x, y);
-                    }
-                    generateEvent(ev.getAction(), ev);
-                } else {
-                    put(pid, index, x, y, ev);
-                }
-                break;
-            case MotionEvent.ACTION_MOVE:
-                for(int i=0; i < ev.getPointerCount(); i++) {
-                    pid = ev.getPointerId(i);
-                    position(pid, x, y);
-                }
-                generateEvent(ev.getAction(), ev);
-                break;
-            case MotionEvent.ACTION_POINTER_UP:
-            case MotionEvent.ACTION_UP:
-                pid = ev.getPointerId(index);
-                lift(pid, index, x, y, ev);
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                cancel(ev);
-                break;
-            default:
-                Log.v(TAG, "Didn't process ");
-                printSamples(TAG, ev);
-
-        }
-    }
-
-    private TouchEventTranslator put(int id, int index, float x, float y, MotionEvent ev) {
-        return put(id, index, x, y, ev.getEventTime(), ev);
-    }
-
-    private TouchEventTranslator put(int id, int index, float x, float y, long ms, MotionEvent ev) {
-        checkFingerExistence(id, false);
-        boolean isInitialDown = (mFingers.size() == 0);
-
-        mFingers.put(id, new PointF(x, y));
-        int n = mFingers.size();
-
-        if (mCache.get(n) == null) {
-            PointerProperties[] properties = new PointerProperties[n];
-            PointerCoords[] coords = new PointerCoords[n];
-            for (int i = 0; i < n; i++) {
-                properties[i] = new PointerProperties();
-                coords[i] = new PointerCoords();
-            }
-            mCache.put(n, new Pair(properties, coords));
-        }
-
-        int action;
-        if (isInitialDown) {
-            action = MotionEvent.ACTION_DOWN;
-        } else {
-            action = MotionEvent.ACTION_POINTER_DOWN;
-            // Set the id of the changed pointer.
-            action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
-        }
-        generateEvent(action, ms, ev);
-        return this;
-    }
-
-    public TouchEventTranslator position(int id, float x, float y) {
-        checkFingerExistence(id, true);
-        mFingers.get(id).set(x, y);
-        return this;
-    }
-
-    private TouchEventTranslator lift(int id, int index, MotionEvent ev) {
-        checkFingerExistence(id, true);
-        boolean isFinalUp = (mFingers.size() == 1);
-        int action;
-        if (isFinalUp) {
-            action = MotionEvent.ACTION_UP;
-        } else {
-            action = MotionEvent.ACTION_POINTER_UP;
-            // Set the id of the changed pointer.
-            action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
-        }
-        generateEvent(action, ev);
-        mFingers.remove(id);
-        return this;
-    }
-
-    private TouchEventTranslator lift(int id, int index, float x, float y, MotionEvent ev) {
-        checkFingerExistence(id, true);
-        mFingers.get(id).set(x, y);
-        return lift(id, index, ev);
-    }
-
-    public TouchEventTranslator cancel(MotionEvent ev) {
-        generateEvent(MotionEvent.ACTION_CANCEL, ev);
-        mFingers.clear();
-        return this;
-    }
-
-    private void checkFingerExistence(int id, boolean shouldExist) {
-        if (shouldExist != (mFingers.get(id, null) != null)) {
-            throw new IllegalArgumentException(
-                    shouldExist ? "Finger does not exist" : "Finger already exists");
-        }
-    }
-
-
-    /**
-     * Used to debug MotionEvents being sent/received.
-     */
-    public void printSamples(String msg, MotionEvent ev) {
-        System.out.printf("%s %s", msg, MotionEvent.actionToString(ev.getActionMasked()));
-        final int pointerCount = ev.getPointerCount();
-        System.out.printf("#%d/%d", ev.getActionIndex(), pointerCount);
-        System.out.printf(" t=%d:", ev.getEventTime());
-        for (int p = 0; p < pointerCount; p++) {
-            System.out.printf("  id=%d: (%f,%f)",
-                    ev.getPointerId(p), ev.getX(p), ev.getY(p));
-        }
-        System.out.println();
-    }
-
-    private void generateEvent(int action, MotionEvent ev) {
-        generateEvent(action, ev.getEventTime(), ev);
-    }
-
-    private void generateEvent(int action, long ms, MotionEvent ev) {
-        Pair<PointerProperties[], PointerCoords[]> state = getFingerState();
-        MotionEvent event = MotionEvent.obtain(
-                mDownEvents.get(0).timeStamp,
-                ms,
-                action,
-                state.first.length,
-                state.first,
-                state.second,
-                ev.getMetaState(),
-                ev.getButtonState() /* buttonState */,
-                ev.getXPrecision() /* xPrecision */,
-                ev.getYPrecision() /* yPrecision */,
-                ev.getDeviceId(),
-                ev.getEdgeFlags(),
-                ev.getSource(),
-                ev.getFlags() /* flags */);
-        if (DEBUG) {
-            printSamples(TAG + " generateEvent", event);
-        }
-        if (event.getPointerId(event.getActionIndex()) < 0) {
-            printSamples(TAG + "generateEvent", event);
-            throw new IllegalStateException(event.getActionIndex() + " not found in MotionEvent");
-        }
-        mListener.accept(event);
-        event.recycle();
-    }
-
-    /**
-     * Returns the description of the fingers' state expected by MotionEvent.
-     */
-    private Pair<PointerProperties[], PointerCoords[]> getFingerState() {
-        int nFingers = mFingers.size();
-
-        Pair<PointerProperties[], PointerCoords[]> result = mCache.get(nFingers);
-        PointerProperties[] properties = result.first;
-        PointerCoords[] coordinates = result.second;
-
-        int index = 0;
-        for (int i = 0; i < mFingers.size(); i++) {
-            int id = mFingers.keyAt(i);
-            PointF location = mFingers.get(id);
-
-            PointerProperties property = properties[i];
-            property.id = id;
-            property.toolType = MotionEvent.TOOL_TYPE_FINGER;
-            properties[index] = property;
-
-            PointerCoords coordinate = coordinates[i];
-            coordinate.x = location.x;
-            coordinate.y = location.y;
-            coordinate.pressure = 1.0f;
-            coordinates[index] = coordinate;
-
-            index++;
-        }
-        return mCache.get(nFingers);
-    }
-}
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index c1ba780..53686ca 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -152,6 +152,9 @@
     }
 
     private TouchController findControllerToHandleTouch(MotionEvent ev) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.EVENTS_TO_OVERVIEW_MISSING_TAG, "findControllerToHandleTouch " + ev);
+        }
         if (shouldDisableGestures(ev)) return null;
 
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
@@ -314,6 +317,9 @@
      * Proxies the touch events to the gesture handlers
      */
     public boolean proxyTouchEvent(MotionEvent ev) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.EVENTS_TO_OVERVIEW_MISSING_TAG, "proxyTouchEvent " + ev);
+        }
         boolean handled;
         if (mProxyTouchController != null) {
             handled = mProxyTouchController.onControllerTouchEvent(ev);
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 17ff66e..5cc64dc 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -93,4 +93,6 @@
 
     public static void resetPendingActivityResults(Launcher launcher, int requestCode) { }
 
+    public static void clearSwipeSharedState(boolean finishAnimation) {}
+
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index e663e70..abaad20 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -20,7 +20,6 @@
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
 
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import static java.lang.System.exit;
 
@@ -38,10 +37,7 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.BySelector;
-import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.Launcher;
@@ -50,7 +46,6 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.model.AppLaunchTracker;
@@ -86,7 +81,6 @@
     public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
     public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
 
-    public static final long SHORT_UI_TIMEOUT = 300;
     public static final long DEFAULT_UI_TIMEOUT = 10000;
     private static final String TAG = "AbstractLauncherUiTest";
 
@@ -125,7 +119,7 @@
     protected TestRule getRulesInsideActivityMonitor() {
         return RuleChain.
                 outerRule(new PortraitLandscapeRunner(this)).
-                around(new FailureWatcher(this));
+                around(new FailureWatcher(mDevice));
     }
 
     @Rule
@@ -181,31 +175,6 @@
     }
 
     /**
-     * Scrolls the {@param container} until it finds an object matching {@param condition}.
-     *
-     * @return the matching object.
-     */
-    protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) {
-        final int margin = ResourceUtils.getNavbarSize(
-                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
-        container.setGestureMargins(0, 0, 0, margin);
-
-        int i = 0;
-        for (; ; ) {
-            // findObject can only execute after spring settles.
-            mDevice.wait(Until.findObject(condition), SHORT_UI_TIMEOUT);
-            UiObject2 widget = container.findObject(condition);
-            if (widget != null && widget.getVisibleBounds().intersects(
-                    0, 0, mDevice.getDisplayWidth(),
-                    mDevice.getDisplayHeight() - margin)) {
-                return widget;
-            }
-            if (++i > 40) fail("Too many attempts");
-            container.scroll(Direction.DOWN, 1f);
-        }
-    }
-
-    /**
      * Removes all icons from homescreen and hotseat.
      */
     public void clearHomescreen() throws Throwable {
diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
index 48335a6..58c74ce 100644
--- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
+++ b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
@@ -25,26 +25,23 @@
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.OutputStreamWriter;
 
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.UiSelector;
-
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class DefaultLayoutProviderTest extends AbstractLauncherUiTest {
@@ -71,7 +68,6 @@
     }
 
     @Test
-    // Convert test to TAPL; b/131116002
     public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
         writeLayout(new LauncherLayoutBuilder().atHotseat(0).putApp(SETTINGS_APP, SETTINGS_APP));
 
@@ -79,14 +75,10 @@
         mActivityMonitor.startLauncher();
         waitForModelLoaded();
 
-        // Verify widget present
-        UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
-                .description(getSettingsApp().getLabel().toString());
-        assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+        mLauncher.getWorkspace().getHotseatAppIcon(getSettingsApp().getLabel().toString());
     }
 
     @Test
-    // Convert test to TAPL; b/131116002
     public void testCustomProfileLoaded_with_widget() throws Exception {
         // A non-restored widget with no config screen gets restored automatically.
         LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
@@ -100,13 +92,11 @@
         waitForModelLoaded();
 
         // Verify widget present
-        UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
-                .className(LauncherAppWidgetHostView.class).description(info.label);
-        assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+        assertTrue("Widget is not present",
+                mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
     }
 
     @Test
-    // Convert test to TAPL; b/131116002
     public void testCustomProfileLoaded_with_folder() throws Exception {
         writeLayout(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy)
                 .addApp(SETTINGS_APP, SETTINGS_APP)
@@ -118,10 +108,7 @@
         mActivityMonitor.startLauncher();
         waitForModelLoaded();
 
-        // Verify widget present
-        UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
-                .descriptionContains(mTargetContext.getString(android.R.string.copy));
-        assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+        mLauncher.getWorkspace().getHotseatFolder("Folder: Copy");
     }
 
     @After
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 06c56f2..4dab44f 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -216,7 +216,8 @@
             final AppIcon app = allApps.getAppIcon("TestActivity7");
             assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
             test.executeOnLauncher(launcher -> assertTrue(
-                    "Launcher activity is the top activity; expecting another activity to be the top "
+                    "Launcher activity is the top activity; expecting another activity to be the "
+                            + "top "
                             + "one",
                     test.isInBackground(launcher)));
         } finally {
@@ -275,8 +276,6 @@
     @Test
     @PortraitLandscape
     public void testLaunchMenuItem() throws Exception {
-        if (!TestHelpers.isInLauncherProcess()) return;
-
         final AllApps allApps = mLauncher.
                 getWorkspace().
                 switchToAllApps();
@@ -306,11 +305,8 @@
                 switchToAllApps();
         allApps.freeze();
         try {
-            allApps.
-                    getAppIcon(APP_NAME).
-                    dragToWorkspace().
-                    getWorkspaceAppIcon(APP_NAME).
-                    launch(getAppPackageName());
+            allApps.getAppIcon(APP_NAME).dragToWorkspace();
+            mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
         } finally {
             allApps.unfreeze();
         }
@@ -323,8 +319,6 @@
     @Test
     @PortraitLandscape
     public void testDragShortcut() throws Throwable {
-        if (!TestHelpers.isInLauncherProcess()) return;
-
         // 1. Open all apps and wait for load complete.
         // 2. Find the app and long press it to show shortcuts.
         // 3. Press icon center until shortcuts appear
@@ -339,13 +333,8 @@
                     getMenuItem(0);
             final String shortcutName = menuItem.getText();
 
-            // 4. Drag the first shortcut to the home screen.
-            // 5. Verify that the shortcut works on home screen
-            //    (the app opens and has the same text as the shortcut).
-            menuItem.
-                    dragToWorkspace().
-                    getWorkspaceAppIcon(shortcutName).
-                    launch(getAppPackageName());
+            menuItem.dragToWorkspace();
+            mLauncher.getWorkspace().getWorkspaceAppIcon(shortcutName).launch(getAppPackageName());
         } finally {
             allApps.unfreeze();
         }
diff --git a/tests/src/com/android/launcher3/ui/TestViewHelpers.java b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
index d13d319..d0df664 100644
--- a/tests/src/com/android/launcher3/ui/TestViewHelpers.java
+++ b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
@@ -18,28 +18,15 @@
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 import static androidx.test.InstrumentationRegistry.getTargetContext;
 
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
 import android.content.ComponentName;
-import android.content.Context;
-import android.graphics.Point;
 import android.os.Process;
-import android.os.SystemClock;
 import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.R;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.testcomponent.AppWidgetNoConfig;
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
@@ -50,21 +37,6 @@
 public class TestViewHelpers {
     private static final String TAG = "TestViewHelpers";
 
-    private static UiDevice getDevice() {
-        return UiDevice.getInstance(getInstrumentation());
-    }
-
-    public static UiObject2 findViewById(int id) {
-        return getDevice().wait(Until.findObject(getSelectorForId(id)),
-                AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT);
-    }
-
-    public static BySelector getSelectorForId(int id) {
-        final Context targetContext = getTargetContext();
-        String name = targetContext.getResources().getResourceEntryName(id);
-        return By.res(targetContext.getPackageName(), name);
-    }
-
     /**
      * Finds a widget provider which can fit on the home screen.
      *
@@ -91,92 +63,6 @@
         return info;
     }
 
-    /**
-     * Drags an icon to the center of homescreen.
-     *
-     * @param icon object that is either app icon or shortcut icon
-     */
-    public static void dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts) {
-        Point center = icon.getVisibleCenter();
-
-        // Action Down
-        final long downTime = SystemClock.uptimeMillis();
-        sendPointer(downTime, MotionEvent.ACTION_DOWN, center);
-
-        UiObject2 dragLayer = findViewById(R.id.drag_layer);
-
-        if (expectedToShowShortcuts) {
-            // Make sure shortcuts show up, and then move a bit to hide them.
-            assertNotNull(findViewById(R.id.deep_shortcuts_container));
-
-            Point moveLocation = new Point(center);
-            int distanceToMove =
-                    getTargetContext().getResources().getDimensionPixelSize(
-                            R.dimen.deep_shortcuts_start_drag_threshold) + 50;
-            if (moveLocation.y - distanceToMove >= dragLayer.getVisibleBounds().top) {
-                moveLocation.y -= distanceToMove;
-            } else {
-                moveLocation.y += distanceToMove;
-            }
-            movePointer(downTime, center, moveLocation);
-
-            assertNull(findViewById(R.id.deep_shortcuts_container));
-        }
-
-        // Wait until Remove/Delete target is visible
-        assertNotNull(findViewById(R.id.delete_target_text));
-
-        Point moveLocation = dragLayer.getVisibleCenter();
-
-        // Move to center
-        movePointer(downTime, center, moveLocation);
-        sendPointer(downTime, MotionEvent.ACTION_UP, moveLocation);
-
-        // Wait until remove target is gone.
-        getDevice().wait(Until.gone(getSelectorForId(R.id.delete_target_text)),
-                AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT);
-    }
-
-    private static void movePointer(long downTime, Point from, Point to) {
-        while (!from.equals(to)) {
-            try {
-                Thread.sleep(20);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-            from.x = getNextMoveValue(to.x, from.x);
-            from.y = getNextMoveValue(to.y, from.y);
-            sendPointer(downTime, MotionEvent.ACTION_MOVE, from);
-        }
-    }
-
-    private static int getNextMoveValue(int targetValue, int oldValue) {
-        if (targetValue - oldValue > 10) {
-            return oldValue + 10;
-        } else if (targetValue - oldValue < -10) {
-            return oldValue - 10;
-        } else {
-            return targetValue;
-        }
-    }
-
-    public static void sendPointer(long downTime, int action, Point point) {
-        MotionEvent event = MotionEvent.obtain(downTime,
-                SystemClock.uptimeMillis(), action, point.x, point.y, 0);
-        getInstrumentation().sendPointerSync(event);
-        event.recycle();
-    }
-
-    /**
-     * Opens widget tray and returns the recycler view.
-     */
-    public static UiObject2 openWidgetsTray() {
-        final UiDevice device = getDevice();
-        device.pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
-        device.waitForIdle();
-        return findViewById(R.id.widgets_list_view);
-    }
-
     public static View findChildView(ViewGroup parent, Function<View, Boolean> condition) {
         for (int i = 0; i < parent.getChildCount(); ++i) {
             final View child = parent.getChildAt(i);
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 5eb5f19..dc72bda 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -27,20 +27,18 @@
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.tapl.Widgets;
 import com.android.launcher3.testcomponent.WidgetConfigActivity;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.Condition;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.WidgetCell;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -71,7 +69,6 @@
     }
 
     @Test
-    // Convert test to TAPL b/131116002
     public void testWidgetConfig() throws Throwable {
         runTest(false, true);
     }
@@ -83,7 +80,6 @@
     }
 
     @Test
-    // Convert test to TAPL b/131116002
     public void testConfigCancelled() throws Throwable {
         runTest(false, false);
     }
@@ -104,15 +100,13 @@
         clearHomescreen();
         mActivityMonitor.startLauncher();
 
-        // Open widget tray and wait for load complete.
-        final UiObject2 widgetContainer = TestViewHelpers.openWidgetsTray();
-        Wait.atMost(null, Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT);
+        final Widgets widgets = mLauncher.getWorkspace().openAllWidgets();
 
         // Drag widget to homescreen
         WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
-        UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
-                .hasDescendant(By.text(mWidgetInfo.getLabel(mTargetContext.getPackageManager()))));
-        TestViewHelpers.dragToWorkspace(widget, false);
+        widgets.
+                getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager())).
+                dragToWorkspace();
         // Widget id for which the config activity was opened
         mWidgetId = monitor.getWidgetId();
 
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 0061568..4529a80 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -19,20 +19,12 @@
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
-import android.view.View;
 
-import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.Condition;
-import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.WidgetCell;
 
 import org.junit.Ignore;
 import org.junit.Rule;
@@ -61,7 +53,6 @@
         performTest();
     }
 
-    // Convert to TAPL b/131116002
     private void performTest() throws Throwable {
         clearHomescreen();
         mActivityMonitor.startLauncher();
@@ -69,22 +60,15 @@
         final LauncherAppWidgetProviderInfo widgetInfo =
                 TestViewHelpers.findWidgetProvider(this, false /* hasConfigureScreen */);
 
-        // Open widget tray and wait for load complete.
-        final UiObject2 widgetContainer = TestViewHelpers.openWidgetsTray();
-        Wait.atMost(null, Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT);
+        mLauncher.
+                getWorkspace().
+                openAllWidgets().
+                getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager())).
+                dragToWorkspace();
 
-        // Drag widget to homescreen
-        UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
-                .hasDescendant(By.text(widgetInfo.getLabel(mTargetContext.getPackageManager()))));
-        TestViewHelpers.dragToWorkspace(widget, false);
-
-        assertTrue(mActivityMonitor.itemExists(new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View view) {
-                return info instanceof LauncherAppWidgetInfo &&
+        assertTrue(mActivityMonitor.itemExists(
+                (info, view) -> info instanceof LauncherAppWidgetInfo &&
                         ((LauncherAppWidgetInfo) info).providerName.getClassName().equals(
-                                widgetInfo.provider.getClassName());
-            }
-        }).call());
+                                widgetInfo.provider.getClassName())).call());
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 874ff19..d36126b 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -15,8 +15,9 @@
  */
 package com.android.launcher3.ui.widget;
 
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -24,43 +25,37 @@
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
-import android.content.ContentValues;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.os.Bundle;
 
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.launcher3.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
 import com.android.launcher3.widget.WidgetHostViewLoader;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Set;
 
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.UiSelector;
-import java.util.concurrent.Callable;
-
 /**
  * Tests for bind widget flow.
  *
@@ -131,16 +126,14 @@
 
         setupContents(item);
 
-        // Since there is no widget to verify, just wait until the workspace is ready.
-        // TODO: fix LauncherInstrumentation#LAUNCHER_PKG
-        mLauncher.getWorkspace();
+        final Workspace workspace = mLauncher.getWorkspace();
         // Item deleted from db
         mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
                 null, null, null, null, null);
         assertEquals(0, mCursor.getCount());
 
         // The view does not exist
-        assertFalse(mDevice.findObject(new UiSelector().description(info.label)).exists());
+        assertTrue("Widget exists", workspace.tryGetWidget(info.label, 0) == null);
     }
 
     @Test
@@ -189,12 +182,8 @@
 
         setupContents(item);
 
-        // Since there is no widget to verify, just wait until the workspace is ready.
-        // TODO: fix LauncherInstrumentation#LAUNCHER_PKG
-        mLauncher.getWorkspace();
-        // The view does not exist
-        assertFalse(mDevice.findObject(
-                new UiSelector().className(PendingAppWidgetHostView.class)).exists());
+        assertTrue("Pending widget exists",
+                mLauncher.getWorkspace().tryGetPendingWidget(0) == null);
         // Item deleted from db
         mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
                 null, null, null, null, null);
@@ -258,12 +247,12 @@
      * widget class is displayed on the homescreen.
      */
     private void setupContents(LauncherAppWidgetInfo item) {
-        int screenId = Workspace.FIRST_SCREEN_ID;
+        int screenId = FIRST_SCREEN_ID;
         // Update the screen id counter for the provider.
         LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
 
-        if (screenId > Workspace.FIRST_SCREEN_ID) {
-            screenId = Workspace.FIRST_SCREEN_ID;
+        if (screenId > FIRST_SCREEN_ID) {
+            screenId = FIRST_SCREEN_ID;
         }
 
         // Insert the item
@@ -283,15 +272,13 @@
     }
 
     private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
-        UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
-                .className(LauncherAppWidgetHostView.class).description(info.label);
-        assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+        assertTrue("Widget is not present",
+                mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
     }
 
     private void verifyPendingWidgetPresent() {
-        UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
-                .className(PendingAppWidgetHostView.class);
-        assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+        assertTrue("Pending widget is not present",
+                mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT) != null);
     }
 
     /**
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 2766a3e..6122dae 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -28,9 +28,6 @@
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppWidgetInfo;
@@ -39,6 +36,7 @@
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.tapl.AddToHomeScreenPrompt;
 import com.android.launcher3.testcomponent.AppWidgetNoConfig;
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
 import com.android.launcher3.testcomponent.RequestPinItemActivity;
@@ -46,7 +44,6 @@
 import com.android.launcher3.util.Condition;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.WidgetCell;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -81,15 +78,10 @@
 
     @Test
     public void testPinWidgetNoConfig() throws Throwable {
-        runTest("pinWidgetNoConfig", true, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View view) {
-                return info instanceof LauncherAppWidgetInfo &&
-                        ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
-                        ((LauncherAppWidgetInfo) info).providerName.getClassName()
-                                .equals(AppWidgetNoConfig.class.getName());
-            }
-        });
+        runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo &&
+                ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+                ((LauncherAppWidgetInfo) info).providerName.getClassName()
+                        .equals(AppWidgetNoConfig.class.getName()));
     }
 
         @Test
@@ -99,28 +91,19 @@
                 RequestPinItemActivity.class, "setRemoteViewColor").putExtra(
                 RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED);
 
-        runTest("pinWidgetNoConfig", true, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View view) {
-                return info instanceof LauncherAppWidgetInfo &&
-                        ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
-                        ((LauncherAppWidgetInfo) info).providerName.getClassName()
-                                .equals(AppWidgetNoConfig.class.getName());
-            }
-        }, command);
+        runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo &&
+                ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+                ((LauncherAppWidgetInfo) info).providerName.getClassName()
+                        .equals(AppWidgetNoConfig.class.getName()), command);
     }
 
     @Test
     public void testPinWidgetWithConfig() throws Throwable {
-        runTest("pinWidgetWithConfig", true, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View view) {
-                return info instanceof LauncherAppWidgetInfo &&
+        runTest("pinWidgetWithConfig", true,
+                (info, view) -> info instanceof LauncherAppWidgetInfo &&
                         ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
                         ((LauncherAppWidgetInfo) info).providerName.getClassName()
-                                .equals(AppWidgetWithConfig.class.getName());
-            }
-        });
+                                .equals(AppWidgetWithConfig.class.getName()));
     }
 
     @Test
@@ -174,15 +157,11 @@
         // call the requested method to start the flow
         mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
                 RequestPinItemActivity.class, activityMethod));
-        UiObject2 widgetCell = mDevice.wait(
-                Until.findObject(By.clazz(WidgetCell.class)), DEFAULT_ACTIVITY_TIMEOUT);
-        assertNotNull(widgetCell);
+        final AddToHomeScreenPrompt addToHomeScreenPrompt = mLauncher.getAddToHomeScreenPrompt();
 
         // Accept confirmation:
         BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction);
-        mDevice.wait(Until.findObject(
-                By.text(mLauncher.isAvd() ? "ADD AUTOMATICALLY" : "Add automatically")),
-                DEFAULT_UI_TIMEOUT).click();
+        addToHomeScreenPrompt.addAutomatically();
         Intent result = resultReceiver.blockingGetIntent();
         assertNotNull(result);
         mAppWidgetId = result.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 09cc98d..eef2f24 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -4,7 +4,7 @@
 
 import android.util.Log;
 
-import com.android.launcher3.ui.AbstractLauncherUiTest;
+import androidx.test.uiautomator.UiDevice;
 
 import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
@@ -16,16 +16,16 @@
 public class FailureWatcher extends TestWatcher {
     private static final String TAG = "FailureWatcher";
     private static int sScreenshotCount = 0;
-    private AbstractLauncherUiTest mAbstractLauncherUiTest;
+    final private UiDevice mDevice;
 
-    public FailureWatcher(AbstractLauncherUiTest abstractLauncherUiTest) {
-        mAbstractLauncherUiTest = abstractLauncherUiTest;
+    public FailureWatcher(UiDevice device) {
+        mDevice = device;
     }
 
     private void dumpViewHierarchy() {
         final ByteArrayOutputStream stream = new ByteArrayOutputStream();
         try {
-            mAbstractLauncherUiTest.getDevice().dumpWindowHierarchy(stream);
+            mDevice.dumpWindowHierarchy(stream);
             stream.flush();
             stream.close();
             for (String line : stream.toString().split("\\r?\\n")) {
@@ -38,7 +38,7 @@
 
     @Override
     protected void failed(Throwable e, Description description) {
-        if (mAbstractLauncherUiTest.getDevice() == null) return;
+        if (mDevice == null) return;
         final String pathname = getInstrumentation().getTargetContext().
                 getFilesDir().getPath() + "/TaplTestScreenshot" + sScreenshotCount++ + ".png";
         Log.e(TAG, "Failed test " + description.getMethodName() +
@@ -48,12 +48,12 @@
         dumpViewHierarchy();
 
         try {
-            final String dumpsysResult = mAbstractLauncherUiTest.getDevice().executeShellCommand(
+            final String dumpsysResult = mDevice.executeShellCommand(
                     "dumpsys activity service TouchInteractionService");
             Log.d(TAG, "TouchInteractionService: " + dumpsysResult);
         } catch (IOException ex) {
         }
 
-        mAbstractLauncherUiTest.getDevice().takeScreenshot(new File(pathname));
+        mDevice.takeScreenshot(new File(pathname));
     }
 }
diff --git a/tests/tapl/AndroidManifest.xml b/tests/tapl/AndroidManifest.xml
index 0207e2b..1065446 100644
--- a/tests/tapl/AndroidManifest.xml
+++ b/tests/tapl/AndroidManifest.xml
@@ -23,4 +23,6 @@
 >
 
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
 </manifest>
diff --git a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
new file mode 100644
index 0000000..7f561a2
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiObject2;
+
+public class AddToHomeScreenPrompt {
+    private final LauncherInstrumentation mLauncher;
+    private final UiObject2 mWidgetCell;
+
+    AddToHomeScreenPrompt(LauncherInstrumentation launcher) {
+        mLauncher = launcher;
+        mWidgetCell = launcher.waitForLauncherObject(By.clazz(
+                "com.android.launcher3.widget.WidgetCell"));
+        mLauncher.assertNotNull("Can't find widget cell object", mWidgetCell);
+    }
+
+    public void addAutomatically() {
+        mLauncher.waitForObjectInContainer(
+                mWidgetCell.getParent().getParent().getParent().getParent(),
+                By.text(LauncherInstrumentation.isAvd()
+                        ? "ADD AUTOMATICALLY"
+                        : "Add automatically")).
+                click();
+    }
+}
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..6e6734d
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Folder.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+import android.widget.FrameLayout;
+
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * App folder in workspace/
+ */
+public final class Folder {
+    Folder(LauncherInstrumentation launcher, UiObject2 icon) {
+    }
+
+    static BySelector getSelector(String folderName, LauncherInstrumentation launcher) {
+        return By.clazz(FrameLayout.class).desc(folderName).pkg(launcher.getLauncherPackageName());
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index d4bdafa..04b8019 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -68,7 +68,7 @@
     /**
      * Drags an object to the center of homescreen.
      */
-    public Workspace dragToWorkspace() {
+    public void dragToWorkspace() {
         final Point launchableCenter = getObject().getVisibleCenter();
         final Point displaySize = mLauncher.getRealDisplaySize();
         final int width = displaySize.x / 2;
@@ -80,10 +80,6 @@
                                 launchableCenter.x - width / 2 : launchableCenter.x + width / 2,
                         displaySize.y / 2),
                 getLongPressIndicator());
-        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                "dragged launchable to workspace")) {
-            return new Workspace(mLauncher);
-        }
     }
 
     protected abstract String getLongPressIndicator();
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 7171bf9..55ccb1c 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -39,6 +39,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.DropBoxManager;
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.text.TextUtils;
@@ -263,10 +264,108 @@
         }
     }
 
+    private String getAnomalyMessage() {
+        final UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
+        if (object != null) {
+            return "System alert popup is visible: " + object.getText();
+        }
+
+        if (!mDevice.hasObject(By.textStartsWith(""))) return "Screen is empty";
+
+        return null;
+    }
+
+    private static String truncateCrash(String text, int maxLines) {
+        String[] lines = text.split("\\r?\\n");
+        StringBuilder ret = new StringBuilder();
+        for (int i = 0; i < maxLines && i < lines.length; i++) {
+            ret.append(lines[i]);
+            ret.append('\n');
+        }
+        if (lines.length > maxLines) {
+            ret.append("... ");
+            ret.append(lines.length - maxLines);
+            ret.append(" more lines truncated ...\n");
+        }
+        return ret.toString();
+    }
+
+    private String checkCrash(String label) {
+        DropBoxManager dropbox = (DropBoxManager) getContext().getSystemService(
+                Context.DROPBOX_SERVICE);
+        Assert.assertNotNull("Unable access the DropBoxManager service", dropbox);
+
+        long timestamp = 0;
+        DropBoxManager.Entry entry;
+        int crashCount = 0;
+        StringBuilder errorDetails = new StringBuilder();
+        while (null != (entry = dropbox.getNextEntry(label, timestamp))) {
+            String dropboxSnippet;
+            try {
+                dropboxSnippet = entry.getText(4096);
+            } finally {
+                entry.close();
+            }
+
+            crashCount++;
+            errorDetails.append(label);
+            errorDetails.append(": ");
+            errorDetails.append(truncateCrash(dropboxSnippet, 40));
+            errorDetails.append("    ...\n");
+
+            timestamp = entry.getTimeMillis();
+        }
+        Assert.assertEquals(errorDetails.toString(), 0, crashCount);
+        return crashCount > 0 ? errorDetails.toString() : null;
+    }
+
+    private String getSystemHealthMessage() {
+        try {
+            StringBuilder errors = new StringBuilder();
+
+            final String testPackage = getContext().getPackageName();
+            try {
+                mDevice.executeShellCommand("pm grant " + testPackage +
+                        " android.permission.READ_LOGS");
+                mDevice.executeShellCommand("pm grant " + testPackage +
+                        " android.permission.PACKAGE_USAGE_STATS");
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+
+            final String[] labels = {
+                    "system_server_crash",
+                    "system_server_native_crash",
+                    "system_server_anr",
+            };
+
+            for (String label : labels) {
+                final String crash = checkCrash(label);
+                if (crash != null) errors.append(crash);
+            }
+
+            return errors.length() != 0 ? errors.toString() : null;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
     private void fail(String message) {
-        log("Hierarchy dump for: " + getContextDescription() + message);
+        message = "http://go/tapl : " + getContextDescription() + message;
+
+        final String anomaly = getAnomalyMessage();
+        if (anomaly != null) message = anomaly + ", which causes:\n" + message;
+
+        final String systemHealth = getSystemHealthMessage();
+        if (systemHealth != null) {
+            message = message + ", which might be a consequence of system health problems:\n<<<\n"
+                    + systemHealth + "\n>>>";
+        }
+
+        log("Hierarchy dump for: " + message);
         dumpViewHierarchy();
-        Assert.fail("http://go/tapl : " + getContextDescription() + message);
+
+        Assert.fail(message);
     }
 
     private String getContextDescription() {
@@ -502,6 +601,13 @@
         }
     }
 
+    @NonNull
+    public AddToHomeScreenPrompt getAddToHomeScreenPrompt() {
+        try (LauncherInstrumentation.Closable c = addContextLayer("want to get widget cell")) {
+            return new AddToHomeScreenPrompt(this);
+        }
+    }
+
     /**
      * Gets the Overview object if the current state is showing the overview panel. Fails if the
      * launcher is not in that state.
@@ -516,17 +622,6 @@
     }
 
     /**
-     * Gets the Base overview object if either Launcher is in overview state or the fallback
-     * overview activity is showing. Fails otherwise.
-     *
-     * @return BaseOverview object.
-     */
-    @NonNull
-    public BaseOverview getBaseOverview() {
-        return new BaseOverview(this);
-    }
-
-    /**
      * Gets the All Apps object if the current state is showing the all apps panel opened by swiping
      * from workspace. Fails if the launcher is not in that state. Please don't call this method if
      * App Apps was opened by swiping up from Overview, as it won't fail and will return an
@@ -617,6 +712,16 @@
     }
 
     @NonNull
+    UiObject2 waitForLauncherObject(BySelector selector) {
+        return waitForObjectBySelector(By.copy(selector).pkg(getLauncherPackageName()));
+    }
+
+    @NonNull
+    UiObject2 tryWaitForLauncherObject(BySelector selector, long timeout) {
+        return tryWaitForObjectBySelector(By.copy(selector).pkg(getLauncherPackageName()), timeout);
+    }
+
+    @NonNull
     UiObject2 waitForFallbackLauncherObject(String resName) {
         return waitForObjectBySelector(getFallbackLauncherObjectSelector(resName));
     }
@@ -627,6 +732,10 @@
         return object;
     }
 
+    private UiObject2 tryWaitForObjectBySelector(BySelector selector, long timeout) {
+        return mDevice.wait(Until.findObject(selector), timeout);
+    }
+
     BySelector getLauncherObjectSelector(String resName) {
         return By.res(getLauncherPackageName(), resName);
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
new file mode 100644
index 0000000..1b6d8c4
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * Widget in workspace or a widget list.
+ */
+public final class Widget extends Launchable {
+    Widget(LauncherInstrumentation launcher, UiObject2 icon) {
+        super(launcher, icon);
+    }
+
+    @Override
+    protected String getLongPressIndicator() {
+        return "drop_target_bar";
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 94003be..2495933 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -16,6 +16,12 @@
 
 package com.android.launcher3.tapl;
 
+import static org.junit.Assert.fail;
+
+import android.graphics.Point;
+
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
@@ -75,4 +81,27 @@
     protected LauncherInstrumentation.ContainerType getContainerType() {
         return LauncherInstrumentation.ContainerType.WIDGETS;
     }
+
+    public Widget getWidget(String label) {
+        final int margin = ResourceUtils.getNavbarSize(
+                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
+        final UiObject2 widgetsContainer = verifyActiveContainer();
+        widgetsContainer.setGestureMargins(0, 0, 0, margin);
+
+        final Point displaySize = mLauncher.getRealDisplaySize();
+
+        int i = 0;
+        final BySelector selector = By.
+                clazz("com.android.launcher3.widget.WidgetCell").
+                hasDescendant(By.text(label));
+
+        for (; ; ) {
+            final UiObject2 widget = mLauncher.tryWaitForLauncherObject(selector, 300);
+            if (widget != null && widget.getVisibleBounds().bottom <= displaySize.y - margin) {
+                return new Widget(mLauncher, widget);
+            }
+            if (++i > 40) fail("Too many attempts");
+            widgetsContainer.scroll(Direction.DOWN, 1f);
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 62050b9..b01b6f3 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -27,6 +27,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
@@ -142,11 +143,17 @@
     }
 
     @NonNull
-    private AppIcon getHotseatAppIcon(String appName) {
+    public AppIcon getHotseatAppIcon(String appName) {
         return new AppIcon(mLauncher, mLauncher.getObjectInContainer(
                 mHotseat, AppIcon.getAppIconSelector(appName, mLauncher)));
     }
 
+    @NonNull
+    public Folder getHotseatFolder(String appName) {
+        return new Folder(mLauncher, mLauncher.getObjectInContainer(
+                mHotseat, Folder.getSelector(appName, mLauncher)));
+    }
+
     static void dragIconToWorkspace(
             LauncherInstrumentation launcher, Launchable launchable, Point dest,
             String longPressIndicator) {
@@ -215,4 +222,19 @@
     protected int getSwipeStartY() {
         return mLauncher.getRealDisplaySize().y - 1;
     }
+
+    @Nullable
+    public Widget tryGetWidget(String label, long timeout) {
+        final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
+                By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label),
+                timeout);
+        return widget != null ? new Widget(mLauncher, widget) : null;
+    }
+
+    @Nullable
+    public Widget tryGetPendingWidget(long timeout) {
+        final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
+                By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout);
+        return widget != null ? new Widget(mLauncher, widget) : null;
+    }
 }
\ No newline at end of file